remove voting artifacts
This commit is contained in:
parent
7fa2acb3c4
commit
7e76511615
|
@ -1,46 +0,0 @@
|
|||
import React, { Fragment, PureComponent } from 'react';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import 'typeface-roboto';
|
||||
import AppBar from './standard/AppBar';
|
||||
import AddPoll from './simple-voting/AddPoll';
|
||||
import PollsList from './simple-voting/PollsList';
|
||||
import StatusLogo from '../ui/components/StatusLogo';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
import { VotingContext } from '../context';
|
||||
|
||||
class Voting extends PureComponent {
|
||||
state = { addPoll: false };
|
||||
|
||||
render(){
|
||||
const { addPoll } = this.state;
|
||||
const togglePoll = () => { this.setState({ addPoll: !addPoll })};
|
||||
return (
|
||||
<VotingContext.Consumer>
|
||||
{({ getPolls, rawPolls, loading }) =>
|
||||
<div>
|
||||
<CssBaseline />
|
||||
<AppBar togglePoll={togglePoll} />
|
||||
{loading && <LinearProgress />}
|
||||
<div style={{ margin: '30px', textAlign: 'center' }}>
|
||||
<img src="images/logo.png" width="200" />
|
||||
<Hidden smUp>
|
||||
<Typography variant="headline" color="inherit">
|
||||
What should we build next?
|
||||
</Typography>
|
||||
</Hidden>
|
||||
</div>
|
||||
<Collapse in={addPoll}>
|
||||
<AddPoll togglePoll={togglePoll} getPolls={getPolls} />
|
||||
</Collapse>
|
||||
{rawPolls && <PollsList rawPolls={rawPolls} />}
|
||||
</div>
|
||||
}
|
||||
</VotingContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Voting
|
|
@ -1,186 +0,0 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import ERC20Token from 'Embark/contracts/ERC20Token';
|
||||
import ProposalCuration from 'Embark/contracts/ProposalCuration';
|
||||
import SNT from 'Embark/contracts/SNT';
|
||||
import React, { PureComponent, Fragment } from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button, Alert } from 'react-bootstrap';
|
||||
import web3 from "Embark/web3";
|
||||
import { withFormik } from 'formik';
|
||||
import FieldGroup from '../standard/FieldGroup';
|
||||
import TokenPermissions from '../standard/TokenPermission'
|
||||
|
||||
const { setSubmitPrice } = ProposalCuration.methods;
|
||||
class InnerForm extends PureComponent {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
submitPrice: "Loading...",
|
||||
canSubmit: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this._loadPrice();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(){
|
||||
this._loadPrice();
|
||||
}
|
||||
|
||||
_loadPrice(){
|
||||
__embarkContext.execWhenReady(async () => {
|
||||
try {
|
||||
let _b = await ProposalCuration.methods.getSubmitPrice(web3.eth.defaultAccount).call();
|
||||
this.setState({
|
||||
submitPrice: _b,
|
||||
canSubmit: true
|
||||
});
|
||||
} catch(err){
|
||||
this.setState({
|
||||
canSubmit: false,
|
||||
submitPrice: "-"
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setPrice = (address = web3.eth.defaultAccount, allowed = true, stakeValue = 1) => {
|
||||
setSubmitPrice(address, allowed, stakeValue)
|
||||
.send()
|
||||
.then(res => {
|
||||
this.setState({ ...this.state, canSubmit: true });
|
||||
console.log(res);
|
||||
})
|
||||
.catch(err => { console.log(err) })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue } = this.props;
|
||||
const { canSubmit } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
{!canSubmit &&
|
||||
<Alert bsStyle="warning">
|
||||
Account not allowed to submit proposals <Button onClick={(e) => this.setPrice()}>Click to enable (Admin only)</Button>
|
||||
</Alert>
|
||||
}
|
||||
<TokenPermissions methods={SNT.methods} spender={ProposalCuration._address} symbol='SNT' />
|
||||
<hr/>
|
||||
<h2>Add proposal</h2>
|
||||
<h3>Price: {this.state.submitPrice}</h3>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FieldGroup
|
||||
id="title"
|
||||
name="title"
|
||||
type="text"
|
||||
label="Title"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.title}
|
||||
/>
|
||||
<FieldGroup
|
||||
id="description"
|
||||
name="description"
|
||||
type="text"
|
||||
label="Description"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.description}
|
||||
/>
|
||||
<FieldGroup
|
||||
id="url"
|
||||
name="url"
|
||||
type="text"
|
||||
label="URL"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.url}
|
||||
/>
|
||||
|
||||
<FieldGroup
|
||||
id="topic"
|
||||
name="topic"
|
||||
type="text"
|
||||
label="Topic"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.topic}
|
||||
/>
|
||||
|
||||
<FieldGroup
|
||||
id="to"
|
||||
name="to"
|
||||
type="text"
|
||||
label="To"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.to}
|
||||
/>
|
||||
|
||||
<FieldGroup
|
||||
id="data"
|
||||
name="data"
|
||||
type="text"
|
||||
label="Data"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.data}
|
||||
/>
|
||||
<FieldGroup
|
||||
id="value"
|
||||
name="value"
|
||||
type="text"
|
||||
label="Value"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.value}
|
||||
/>
|
||||
|
||||
<Button type="submit" disabled={!canSubmit || isSubmitting}>{isSubmitting ? 'Submission in progress' : 'Submit'}</Button>
|
||||
</Form>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const ProposalManager = withFormik({
|
||||
mapPropsToValues: props => ({ title: '', description: '', url: '', data: '0x00', value: '0', topic: '0x00', to: '0x0000000000000000000000000000000000000000' }),
|
||||
validate(values) {},
|
||||
handleSubmit(values, { setSubmitting}){
|
||||
|
||||
let dataObj = {
|
||||
title: values.title,
|
||||
description: values.description,
|
||||
url: values.url,
|
||||
};
|
||||
|
||||
const { toHex } = web3.utils;
|
||||
const { submitProposal } = ProposalCuration.methods;
|
||||
|
||||
EmbarkJS.Storage.saveText(JSON.stringify(dataObj))
|
||||
.then(hash => {
|
||||
const hexHash = toHex(hash);
|
||||
//TODO create toggle for address approval
|
||||
submitProposal(
|
||||
values.topic,
|
||||
values.to,
|
||||
values.value,
|
||||
values.data,
|
||||
hexHash
|
||||
)
|
||||
.send({from: web3.eth.defaultAccount, gasLimit: 1000000})
|
||||
.then(res => {
|
||||
setSubmitting(false);
|
||||
console.log(res);
|
||||
})
|
||||
.catch(err => {
|
||||
setSubmitting(false);
|
||||
//TODO show error
|
||||
console.log('Storage saveText Error: ', err.message)
|
||||
});
|
||||
})
|
||||
}
|
||||
})(InnerForm)
|
||||
|
||||
export default ProposalManager;
|
|
@ -1,121 +0,0 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import PollManager from 'Embark/contracts/PollManager';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { withFormik } from 'formik';
|
||||
|
||||
const oneDayinBlocks = 5760;
|
||||
|
||||
const styles = theme => ({
|
||||
button: {
|
||||
margin: theme.spacing.unit,
|
||||
},
|
||||
extendedIcon: {
|
||||
marginRight: theme.spacing.unit,
|
||||
},
|
||||
textField: {
|
||||
marginLeft: theme.spacing.unit,
|
||||
marginRight: theme.spacing.unit
|
||||
},
|
||||
inputLabel: {
|
||||
fontSize: '16px'
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
textFieldInput: {
|
||||
fontSize: '16px'
|
||||
},
|
||||
textFieldFormLabel: {
|
||||
fontSize: 18,
|
||||
}
|
||||
});
|
||||
|
||||
const InnerForm = ({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
classes
|
||||
}) => (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit} className={classes.form}>
|
||||
<TextField
|
||||
id="description"
|
||||
label="Enter your proposal description"
|
||||
className={classes.textField}
|
||||
value={values.description}
|
||||
onChange={handleChange}
|
||||
margin="normal"
|
||||
fullWidth
|
||||
error={!!errors.description}
|
||||
InputProps={{
|
||||
classes: {
|
||||
input: classes.textFieldInput
|
||||
},
|
||||
}}
|
||||
InputLabelProps={{
|
||||
className: classes.textFieldFormLabel
|
||||
}}
|
||||
helperText={errors.description}
|
||||
/>
|
||||
{!isSubmitting ?
|
||||
<Button type="submit" variant="extendedFab" aria-label="add" className={classes.button}>Submit</Button> :
|
||||
<CircularProgress style={{ margin: '10px 10px 10px 50%' }} />
|
||||
}
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const StyledForm = withStyles(styles)(InnerForm);
|
||||
const AddPoll = withFormik({
|
||||
mapPropsToValues: props => ({ description: ''}),
|
||||
validate(values, props){
|
||||
const errors = {};
|
||||
const { description } = values;
|
||||
if(description.toString().trim() === "") errors.description = true;
|
||||
return errors;
|
||||
},
|
||||
async handleSubmit(values, { setSubmitting, setErrors, props }) {
|
||||
const { description } = values;
|
||||
const { eth: { getBlockNumber } } = window.web3;
|
||||
const { addPoll } = PollManager.methods;
|
||||
const currentBlock = await getBlockNumber();
|
||||
const endTime = currentBlock + (oneDayinBlocks * 90);
|
||||
const toSend = addPoll(endTime, description);
|
||||
|
||||
setSubmitting(true);
|
||||
|
||||
toSend.estimateGas()
|
||||
.then(gasEstimated => {
|
||||
console.log("addPoll gas estimated: "+gasEstimated);
|
||||
return toSend.send({gas: gasEstimated + 100000});
|
||||
})
|
||||
.then(res => {
|
||||
console.log('sucess:', res);
|
||||
props.getPolls();
|
||||
setSubmitting(false);
|
||||
props.togglePoll();
|
||||
})
|
||||
.catch(res => {
|
||||
console.log('fail:', res);
|
||||
setErrors({ 'description': res.message.split('Error:').pop().trim() });
|
||||
})
|
||||
.finally(() => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
})(StyledForm)
|
||||
|
||||
export default AddPoll;
|
|
@ -1,102 +0,0 @@
|
|||
import React, { Fragment, PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import List from '@material-ui/core/List';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
import Checkbox from '@material-ui/core/Checkbox';
|
||||
import { VotingContext } from '../../context';
|
||||
|
||||
export const constants = { MOST_VOTES: 'Most Votes', MOST_VOTERS: 'Most Voters', NEWEST_ADDED: 'Newest Added', ENDING_SOONEST: 'Ending Soonest' };
|
||||
const styles = {
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
function Transition(props) {
|
||||
return <Slide direction="up" {...props} />;
|
||||
}
|
||||
|
||||
const ListButton = ({ name, setPollOrder, selected, handleClose }) => (
|
||||
<Fragment>
|
||||
<ListItem button onClick={() => {
|
||||
setPollOrder(name);
|
||||
handleClose();
|
||||
}}>
|
||||
<Checkbox
|
||||
checked={selected}
|
||||
color="primary"
|
||||
disableRipple
|
||||
/>
|
||||
<ListItemText primary={constants[name]} />
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
class OrderingDialog extends PureComponent {
|
||||
state = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
handleClickOpen = () => {
|
||||
this.setState({ open: true });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { handleClose } = this;
|
||||
return (
|
||||
<VotingContext.Consumer>
|
||||
{({ setPollOrder, pollOrder }) =>
|
||||
<div>
|
||||
<Button color="inherit" variant="outlined" onClick={this.handleClickOpen}>Open Filters</Button>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar className={classes.appBar} onClick={this.handleClose}>
|
||||
<Toolbar>
|
||||
<IconButton color="inherit" aria-label="Close">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="title" color="inherit" className={classes.flex}>
|
||||
close
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<List>
|
||||
{Object.keys(constants).map((name, i) => <ListButton key={i} name={name} setPollOrder={setPollOrder} selected={pollOrder === name} handleClose={handleClose} />)}
|
||||
</List>
|
||||
</Dialog>
|
||||
</div>
|
||||
}
|
||||
</VotingContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OrderingDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(OrderingDialog);
|
|
@ -1,193 +0,0 @@
|
|||
import React, { Fragment, PureComponent } from 'react';
|
||||
import { toString } from 'lodash';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Slider from '@material-ui/lab/Slider';
|
||||
import PollManager from 'Embark/contracts/PollManager';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import web3 from "Embark/web3"
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { VotingContext } from '../../context';
|
||||
|
||||
const styles = {
|
||||
card: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
},
|
||||
thumb: {
|
||||
width: '24px',
|
||||
height: '24px'
|
||||
},
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
};
|
||||
function Transition(props) {
|
||||
return <Slide direction="up" {...props} />;
|
||||
};
|
||||
|
||||
const getIdeaFromStr = str => {
|
||||
const match = str.match(/\(([^)]+)\)/)
|
||||
if (match) return match[1].toLowerCase();
|
||||
return match;
|
||||
}
|
||||
const sortingFn = {
|
||||
MOST_VOTES: (a, b) => b._qvResults - a._qvResults,
|
||||
MOST_VOTERS: (a, b) => b._voters - a._voters,
|
||||
NEWEST_ADDED: (a, b) => b._startBlock - a._startBlock,
|
||||
ENDING_SOONEST: (a, b) => a._endBlock - b._endBlock
|
||||
};
|
||||
class Poll extends PureComponent {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = { value: props.votes, originalValue: props.votes, balance: 0, isSubmitting: false, open: false };
|
||||
}
|
||||
|
||||
handleClickOpen = () => {
|
||||
this.setState({ open: true });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
handleChange = (event, value) => {
|
||||
this.setState({ value })
|
||||
};
|
||||
|
||||
handleClick = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.setState({isSubmitting: true});
|
||||
|
||||
const { customVote, poll, unvote } = PollManager.methods;
|
||||
const { updatePoll, idPoll } = this.props;
|
||||
const { value } = this.state;
|
||||
const { toWei } = web3.utils;
|
||||
|
||||
const balance4Voting = toWei(toString(value * value));
|
||||
const toSend = balance4Voting == 0 ? unvote(idPoll) : customVote(idPoll, balance4Voting);
|
||||
|
||||
toSend.estimateGas()
|
||||
.then(gasEstimated => {
|
||||
console.log("voting gas estimated: " + gasEstimated);
|
||||
return toSend.send({gas: gasEstimated + 100000});
|
||||
})
|
||||
.then(res => {
|
||||
console.log('sucess:', res);
|
||||
this.setState({ isSubmitting: false, originalValue: value });
|
||||
return updatePoll(idPoll);
|
||||
})
|
||||
.catch(res => {
|
||||
console.log('fail:', res, res.messsage);
|
||||
this.setState({ error: res.message })
|
||||
})
|
||||
.finally(() => {
|
||||
this.setState({isSubmitting: false});
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
const {
|
||||
_description,
|
||||
_totalCensus,
|
||||
_voters,
|
||||
_qvResults,
|
||||
_results,
|
||||
_canVote,
|
||||
balance,
|
||||
classes,
|
||||
ideaSites
|
||||
} = this.props;
|
||||
const { value, originalValue, isSubmitting, error } = this.state;
|
||||
const cantVote = balance == 0 || !_canVote;
|
||||
const disableVote = cantVote || isSubmitting;
|
||||
const { fromWei } = web3.utils;
|
||||
const maxValue = Math.floor(Math.sqrt(balance));
|
||||
const buttonText = originalValue != 0 && value != originalValue ? 'Change Vote' : 'Vote';
|
||||
const idea = getIdeaFromStr(_description)
|
||||
const ideaSite = ideaSites && ideaSites.filter(site => site.includes(idea));
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="headline">{_description}</Typography>
|
||||
<Typography variant="subheading" color="textSecondary">
|
||||
<b>Total:</b> {_voters} voters. {_qvResults} votes ({fromWei(_results)} SNT)
|
||||
</Typography>
|
||||
<Typography variant="subheading" color="textSecondary">
|
||||
<b>Your vote:</b> {value} votes ({value * value} SNT)
|
||||
</Typography>
|
||||
{cantVote && <Typography variant="body2" color="error">
|
||||
{balance == 0 && <span>Voting disabled for proposals made when there was no SNT in the account</span>}
|
||||
{balance != 0 && !_canVote && <span>You can not vote on this poll</span>}
|
||||
</Typography>}
|
||||
{error && <Typography variant="body2" color="error">{error}</Typography>}
|
||||
{ideaSite && ideaSite.length > 0 && <Typography onClick={this.handleClickOpen} variant="subheading" color="primary">{ideaSite}</Typography>}
|
||||
{ideaSite && <Dialog
|
||||
fullScreen
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar className={classes.appBar} onClick={this.handleClose}>
|
||||
<Toolbar>
|
||||
<IconButton color="inherit" aria-label="Close">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="title" color="inherit" className={classes.flex}>
|
||||
close
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<div
|
||||
style={{ overflow: "auto", height: '100%', width: '100%', position: "fixed", top: 0, left: 0, zIndex: 1, overflowScrolling: "touch", WebkitOverflowScrolling: "touch" }}
|
||||
>
|
||||
<iframe
|
||||
className="contentIframe"
|
||||
frameBorder="0"
|
||||
src={ideaSite[0]}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
height="100%"
|
||||
width="100%"
|
||||
>
|
||||
</iframe>
|
||||
</div>
|
||||
</Dialog>}
|
||||
</CardContent>
|
||||
{!cantVote && <CardActions className={classes.card}>
|
||||
<Slider style={{ width: '95%' }} classes={{ thumb: classes.thumb }} disabled={disableVote} value={value || 0} min={0} max={maxValue} step={1} onChange={this.handleChange} />
|
||||
{isSubmitting ? <CircularProgress /> : <Button variant="contained" disabled={disableVote} color="primary" onClick={this.handleClick}>{buttonText}</Button>}
|
||||
</CardActions>}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const PollsList = ({ classes }) => (
|
||||
<VotingContext.Consumer>
|
||||
{({ updatePoll, rawPolls, pollOrder, appendToPoll, ideaSites }) =>
|
||||
<Fragment>
|
||||
{rawPolls
|
||||
.sort(sortingFn[pollOrder])
|
||||
.map((poll) => <Poll key={poll._token} classes={classes} appendToPoll={appendToPoll} updatePoll={updatePoll} ideaSites={ideaSites} {...poll} />)}
|
||||
</Fragment>
|
||||
}
|
||||
</VotingContext.Consumer>
|
||||
)
|
||||
|
||||
export default withStyles(styles)(PollsList);
|
|
@ -1,25 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, {Fragment} from 'react';
|
||||
import {Button} from 'react-bootstrap';
|
||||
|
||||
const Paginator = props => {
|
||||
let ln = Math.ceil(props.total / props.recordsByPage);
|
||||
let btnArray = []
|
||||
for(let i = 1; i <= ln; i++){
|
||||
btnArray.push(<Button
|
||||
bsStyle="link"
|
||||
className={i == props.pageNum ? 'current' : ''}
|
||||
onClick={(e) => props.pageHandler(e, i)}>{i}</Button>)
|
||||
}
|
||||
|
||||
return <div>{
|
||||
btnArray.map((component, index) => (
|
||||
<Fragment key={index}>
|
||||
{ component }
|
||||
</Fragment>
|
||||
))
|
||||
}</div>;
|
||||
};
|
||||
|
||||
export default Paginator;
|
|
@ -1,67 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, {Component, Fragment} from 'react';
|
||||
import Proposal from './proposal';
|
||||
import ProposalList from './proposal-list';
|
||||
import Paginator from './paginator';
|
||||
import ProposalManager from 'Embark/contracts/ProposalManager';
|
||||
import ProposalCuration from 'Embark/contracts/ProposalCuration';
|
||||
|
||||
const pageLength = 4;
|
||||
class ProposalContainer extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
proposals: [],
|
||||
total: 0,
|
||||
page: 1
|
||||
};
|
||||
window['ProposalCuration'] = ProposalCuration;
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
__embarkContext.execWhenReady(async () => {
|
||||
ProposalManager.options.address = await ProposalCuration.methods.proposalManager().call();
|
||||
await this.getTotalProposals();
|
||||
await this.getProposalsOnPage(this.state.page);
|
||||
});
|
||||
}
|
||||
|
||||
async getTotalProposals(){
|
||||
let proposalCount = await ProposalManager.methods.getProposalCount().call();
|
||||
this.setState({
|
||||
total: parseInt(proposalCount)
|
||||
});
|
||||
}
|
||||
|
||||
async getProposalsOnPage(pageNum){
|
||||
this.fetchProposals((pageNum - 1) * pageLength, pageLength,
|
||||
_p => {
|
||||
this.setState({
|
||||
proposals: _p,
|
||||
page: pageNum
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fetchProposals(from, qty, cb){
|
||||
let proposalList = [];
|
||||
for(let i = from; i < from + qty && i < this.state.total; i++){
|
||||
let res = await ProposalCuration.methods.proposals(i).call();
|
||||
res.id = i;
|
||||
proposalList.push(res);
|
||||
}
|
||||
cb(proposalList);
|
||||
}
|
||||
|
||||
render(){
|
||||
return <Fragment>
|
||||
<ProposalList proposals={this.state.proposals} />
|
||||
<Paginator total={this.state.total} recordsByPage={pageLength} page={this.state.page} pageHandler={(e, pageNum) => this.getProposalsOnPage(pageNum) } />
|
||||
</Fragment>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ProposalContainer;
|
|
@ -1,11 +0,0 @@
|
|||
import React, {Fragment} from 'react';
|
||||
import Proposal from './proposal';
|
||||
|
||||
const ProposalList = props =>
|
||||
<Fragment>
|
||||
{props.proposals.map((u, i) => (
|
||||
<Proposal key={i} data={u} />
|
||||
))}
|
||||
</Fragment>
|
||||
|
||||
export default ProposalList;
|
|
@ -1,68 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import React from 'react';
|
||||
import $ from 'jquery';
|
||||
import { Button, Alert } from 'react-bootstrap';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import Voting from './voting';
|
||||
|
||||
class Proposal extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
url: null,
|
||||
title: null,
|
||||
description: null,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot){
|
||||
if(prevProps.data.description !== this.props.data.description)
|
||||
this.getProposalData();
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
__embarkContext.execWhenReady(() => {
|
||||
this.getProposalData();
|
||||
});
|
||||
}
|
||||
|
||||
getProposalData(){
|
||||
let hash = web3.utils.toAscii(this.props.data.description);
|
||||
EmbarkJS.Storage.get(hash)
|
||||
.then((content) => {
|
||||
let jsonObj = JSON.parse(content);
|
||||
this.setState({
|
||||
url: jsonObj.url,
|
||||
title: jsonObj.title,
|
||||
description: jsonObj.description
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
if(err){
|
||||
console.log("Storage get Error => " + err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (<div>
|
||||
{
|
||||
this.state.error !== null ?
|
||||
<Alert bsStyle="warning">
|
||||
{ this.state.error }
|
||||
</Alert>
|
||||
: ''
|
||||
}
|
||||
<h3>{ this.state.title }</h3>
|
||||
<p>{ this.state.description }</p>
|
||||
<a href={ this.state.url } target="_blank">{ this.state.url }</a>
|
||||
<Voting proposalId={this.props.data.id} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Proposal;
|
|
@ -1,32 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, {Component} from 'react';
|
||||
import SNT from 'Embark/contracts/SNT'; // TODO change this to SNT
|
||||
|
||||
class StatusBar extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
balance: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(){
|
||||
__embarkContext.execWhenReady(async () => {
|
||||
let _b = await SNT.methods.balanceOf(web3.eth.defaultAccount).call();
|
||||
this.setState({
|
||||
balance: _b
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
return <div className="SNTBalance">
|
||||
SNT Voting Balance: <span>{this.state.balance}</span>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default StatusBar;
|
|
@ -1,13 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React from 'react';
|
||||
import ProposalContainer from './proposal-container';
|
||||
import StatusBar from './status-bar';
|
||||
|
||||
const VotingDapp = props =>
|
||||
<div>
|
||||
<StatusBar {...props} />
|
||||
<ProposalContainer />
|
||||
</div>;
|
||||
|
||||
export default VotingDapp;
|
|
@ -1,159 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, {Component, Fragment} from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import ProposalManager from 'Embark/contracts/ProposalManager';
|
||||
import ProposalCuration from 'Embark/contracts/ProposalCuration';
|
||||
|
||||
class Voting extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
decision: 0,
|
||||
finalResult: null,
|
||||
data: null,
|
||||
tabulationAvailable: false,
|
||||
votingAvailable: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(){
|
||||
__embarkContext.execWhenReady(async () => {
|
||||
this._loadProposalData();
|
||||
});
|
||||
}
|
||||
|
||||
async _loadProposalData() {
|
||||
ProposalManager.options.address = await ProposalCuration.methods.proposalManager().call();
|
||||
const _votingInfo = await ProposalManager.methods.getVoteInfo(this.props.proposalId, web3.eth.defaultAccount).call();
|
||||
const _blockNum = await web3.eth.getBlockNumber();
|
||||
const _data = await ProposalManager.methods.proposals(this.props.proposalId).call();
|
||||
|
||||
const _votingAvailable = await ProposalManager.methods.isVotingAvailable(this.props.proposalId).call();
|
||||
const _tabulationAvailable = await ProposalManager.methods.isTabulationAvailable(this.props.proposalId).call();
|
||||
const _voteTabulated = await ProposalManager.methods.isDelegatorVoteTabulated(this.props.proposalId, web3.eth.defaultAccount).call();
|
||||
const _canCalculateFinalResult = await ProposalManager.methods.canCalculateFinalResult(this.props.proposalId).call();
|
||||
|
||||
this.setState({
|
||||
data: _data,
|
||||
decision: _votingInfo.vote,
|
||||
block: _blockNum,
|
||||
finalResult: _data.result,
|
||||
votingAvailable: _votingAvailable,
|
||||
tabulationAvailable: _tabulationAvailable,
|
||||
finalResultAvailable: _canCalculateFinalResult,
|
||||
voteTabulated: _voteTabulated
|
||||
});
|
||||
}
|
||||
|
||||
async determineFinalResult(e){
|
||||
e.preventDefault();
|
||||
|
||||
let receipt = await ProposalManager.methods.finalResult(this.props.proposalId)
|
||||
.send({from: web3.eth.defaultAccount, gasLimit: 1000000});
|
||||
if(receipt.status == '0x1'){
|
||||
this.setState({
|
||||
finalResult: receipt.events.ProposalResult.returnValues.finalResult,
|
||||
finalResultAvailable: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async tabulateVote(e){
|
||||
e.preventDefault();
|
||||
|
||||
const receipt = await ProposalManager.methods.tabulateVote(this.props.proposalId, web3.eth.defaultAccount)
|
||||
.send({from: web3.eth.defaultAccount, gasLimit: 1000000});
|
||||
|
||||
// TODO: handle error
|
||||
|
||||
this._loadProposalData();
|
||||
}
|
||||
|
||||
async handleClick(e, vote){
|
||||
e.preventDefault();
|
||||
|
||||
let choice = 0;
|
||||
if(vote == 'APPROVE')
|
||||
choice = 2;
|
||||
else
|
||||
choice = 1;
|
||||
|
||||
const proposal = this.props.proposalId;
|
||||
const receipt = await ProposalManager.methods.voteProposal(this.props.proposalId, choice)
|
||||
.send({from: web3.eth.defaultAccount});
|
||||
|
||||
const _votingAvailable = await ProposalManager.methods.isVotingAvailable(this.props.proposalId).call();
|
||||
const _tabulationAvailable = await ProposalManager.methods.isTabulationAvailable(this.props.proposalId).call();
|
||||
const _voteTabulated = await ProposalManager.methods.isDelegatorVoteTabulated(this.props.proposalId, web3.eth.defaultAccount).call();
|
||||
const _canCalculateFinalResult = await ProposalManager.methods.canCalculateFinalResult(this.props.proposalId).call();
|
||||
|
||||
const blockNum = await web3.eth.getBlockNumber();
|
||||
|
||||
if(receipt.status == '0x1'){
|
||||
this.setState({
|
||||
decision: choice,
|
||||
block: blockNum,
|
||||
votingAvailable: _votingAvailable,
|
||||
tabulationAvailable: _tabulationAvailable,
|
||||
finalResultAvailable: _canCalculateFinalResult,
|
||||
voteTabulated: _voteTabulated
|
||||
});
|
||||
}
|
||||
// TODO: handle error
|
||||
}
|
||||
|
||||
render(){
|
||||
console.log(this.state);
|
||||
return <div>
|
||||
{
|
||||
this.state.decision != 0 ?
|
||||
<p>You voted for: <ResultChoice decision={this.state.decision} /></p>
|
||||
: ''
|
||||
}
|
||||
|
||||
{
|
||||
this.state.data != null && this.state.votingAvailable ?
|
||||
<Fragment>
|
||||
<Button onClick={(e) => this.handleClick(e, 'APPROVE') }>Approve</Button>
|
||||
<Button onClick={(e) => this.handleClick(e, 'REJECT') }>Reject</Button>
|
||||
</Fragment>
|
||||
: ''
|
||||
}
|
||||
|
||||
{
|
||||
this.state.data != null && this.state.tabulationAvailable && !this.state.voteTabulated ?
|
||||
<Button onClick={(e) => this.tabulateVote(e) }>Tabulate your vote</Button>
|
||||
: ''
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
this.state.finalResultAvailable ?
|
||||
<Button onClick={(e) => this.determineFinalResult(e) }>Determine final result</Button>
|
||||
: !this.state.tabulationAvailable && !this.state.voteTabulated ?
|
||||
<p>Final results aren't available yet</p> : ''
|
||||
}
|
||||
|
||||
{ this.state.data != null ?
|
||||
<ul>
|
||||
<li>Voting ends on block: {this.state.data.voteBlockEnd }</li>
|
||||
<li>Current Block: { this.state.block }</li>
|
||||
<li>Final Result: <ResultChoice decision={this.state.finalResult} /></li>
|
||||
</ul>
|
||||
: '' }
|
||||
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ResultChoice = props =>
|
||||
<span>{props.decision.toString() == '1' ? 'REJECT' : props.decision.toString() == '2' ? 'APPROVE' : ''}</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default Voting;
|
|
@ -1,3 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export const VotingContext = React.createContext(null);
|
|
@ -2,14 +2,8 @@ import React, { Fragment } from 'react';
|
|||
import ReactDOM from 'react-dom';
|
||||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import PollManager from 'Embark/contracts/PollManager';
|
||||
import AdminView from './components/AdminView';
|
||||
import Voting from './components/Voting';
|
||||
import SNT from 'Embark/contracts/SNT';
|
||||
import { VotingContext } from './context';
|
||||
import Web3Render from './components/standard/Web3Render';
|
||||
import fetchIdeas from './utils/fetchIdeas';
|
||||
import { getPolls, omitPolls } from './utils/polls';
|
||||
import DrawField from './components/draw/DrawField';
|
||||
import ContractClient from './contract_client'
|
||||
window['SNT'] = SNT;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import axios from 'axios';
|
||||
|
||||
const repoFilter = (repo) => {
|
||||
const { path } = repo;
|
||||
if (path.includes('ideas')) {
|
||||
const split = path.split('/');
|
||||
if (split.length < 3) return true;
|
||||
if (path.includes('README')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const convertToUrl = (repo) => {
|
||||
const { path } = repo;
|
||||
const base = 'https://ideas.status.im/';
|
||||
const suffix = path.split('.md')[0];
|
||||
return `${base}${suffix}`;
|
||||
}
|
||||
|
||||
const fetchContent = async () => {
|
||||
const response = await axios.get('https://api.github.com/repos/status-im/ideas/git/trees/master?recursive=1');
|
||||
return response['data']['tree'].filter(repoFilter).map(convertToUrl);
|
||||
}
|
||||
|
||||
const pluckIdeas = async () => {
|
||||
const data = await fetchContent();
|
||||
return data;
|
||||
}
|
||||
export default pluckIdeas;
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import web3 from "Embark/web3"
|
||||
import MiniMeTokenInterface from 'Embark/contracts/MiniMeTokenInterface';
|
||||
import PollManager from 'Embark/contracts/PollManager';
|
||||
|
||||
const excluded = {
|
||||
PROPER_LIGHT_CLIENT_SUPPORT : 3,
|
||||
IMPLEMENT_SECURITY_PRACTICES : 14,
|
||||
SHIP_1_0 : 16
|
||||
};
|
||||
|
||||
export const getBalance = async (idPoll, token, startBlock) => {
|
||||
const { fromWei } = web3.utils;
|
||||
const { balanceOfAt } = MiniMeTokenInterface.methods;
|
||||
MiniMeTokenInterface.options.address = token;
|
||||
const balance = await balanceOfAt(web3.eth.defaultAccount, startBlock - 1).call();
|
||||
return fromWei(balance);
|
||||
}
|
||||
|
||||
export const getVote = async(idPoll) => {
|
||||
const { fromWei } = web3.utils;
|
||||
const votes = await PollManager.methods.getVote(idPoll, web3.eth.defaultAccount).call();
|
||||
return parseInt(Math.sqrt(fromWei(votes)));
|
||||
}
|
||||
|
||||
const fetchPollData = async (index, pollMethod) => {
|
||||
const poll = await pollMethod(index).call();
|
||||
const balance = await getBalance(index, poll._token, poll._startBlock);
|
||||
const votes = await getVote(index);
|
||||
return { ...poll, idPoll: index, balance, votes };
|
||||
}
|
||||
|
||||
export const getPolls = (number, pollMethod) => {
|
||||
const polls = [];
|
||||
for (let i = number-1; i >= 0; i--) {
|
||||
polls.push(fetchPollData(i, pollMethod));
|
||||
}
|
||||
return Promise.all(polls.reverse());
|
||||
}
|
||||
|
||||
const excludedPolls = new Set(Object.values(excluded));
|
||||
const exclusionFilter = (poll, idx) => !excludedPolls.has(idx);
|
||||
export const omitPolls = polls => polls.filter(exclusionFilter);
|
Loading…
Reference in New Issue