Base BallotCard class
This commit is contained in:
parent
67e5fc5ba8
commit
e0ff441448
|
@ -0,0 +1,289 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { observable, action, computed, autorun } from "mobx";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toAscii } from "../helpers";
|
||||
import { constants } from "../constants";
|
||||
import swal from 'sweetalert2';
|
||||
|
||||
const ACCEPT = 1;
|
||||
const REJECT = 2;
|
||||
@inject("commonStore", "contractsStore", "ballotStore", "routing")
|
||||
@observer
|
||||
export class BallotCard extends React.Component {
|
||||
@observable startTime;
|
||||
@observable endTime;
|
||||
@observable timeToFinish;
|
||||
@observable creator;
|
||||
@observable progress;
|
||||
@observable totalVoters;
|
||||
@observable isFinalized;
|
||||
@observable isFiltered;
|
||||
|
||||
@computed get votesForNumber() {
|
||||
let votes = (this.totalVoters + this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesForPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesForNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@computed get votesAgainstNumber() {
|
||||
let votes = (this.totalVoters - this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesAgainstPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesAgainstNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@action("Get start time of keys ballot")
|
||||
getStartTime = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let startTime = await contractsStore[votingType].getStartTime(id);
|
||||
this.startTime = moment.utc(startTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Get end time of keys ballot")
|
||||
getEndTime = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let endTime = await contractsStore[votingType].getEndTime(id);
|
||||
this.endTime = moment.utc(endTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Calculate time to finish")
|
||||
calcTimeToFinish = () => {
|
||||
const now = moment();
|
||||
const finish = moment.utc(this.endTime, 'DD/MM/YYYY h:mm:ss A');
|
||||
let ms = finish.diff(now);
|
||||
if (ms <= 0)
|
||||
return this.timeToFinish = moment(0, "h").format("HH") + ":" + moment(0, "m").format("mm") + ":" + moment(0, "s").format("ss");
|
||||
|
||||
let dur = moment.duration(ms);
|
||||
this.timeToFinish = Math.floor(dur.asHours()) + moment.utc(ms).format(":mm:ss");
|
||||
}
|
||||
|
||||
@action("Get times")
|
||||
getTimes = async () => {
|
||||
await this.getStartTime();
|
||||
await this.getEndTime();
|
||||
this.calcTimeToFinish();
|
||||
}
|
||||
|
||||
@action("Get creator")
|
||||
getCreator = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let votingState = await contractsStore[votingType].votingState(id);
|
||||
this.getValidatorFullname(votingState.creator);
|
||||
}
|
||||
|
||||
@action("Get progress")
|
||||
getProgress = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let progress = await contractsStore[votingType].getProgress(id);
|
||||
this.progress = Number(progress);
|
||||
}
|
||||
|
||||
@action("Get total voters")
|
||||
getTotalVoters = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let totalVoters = await contractsStore[votingType].getTotalVoters(id);
|
||||
this.totalVoters = Number(totalVoters);
|
||||
}
|
||||
|
||||
@action("Get isFinalized")
|
||||
getIsFinalized = async() => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
this.isFinalized = await contractsStore[votingType].getIsFinalized(id);
|
||||
}
|
||||
|
||||
@action("Get validator full name")
|
||||
getValidatorFullname = async (_miningKey) => {
|
||||
const { contractsStore } = this.props;
|
||||
let validator = await contractsStore.validatorMetadata.validators(_miningKey);
|
||||
let firstName = toAscii(validator.firstName);
|
||||
let lastName = toAscii(validator.lastName);
|
||||
let fullName = `${firstName} ${lastName}`
|
||||
console.log("fullname:", validator)
|
||||
this.creator = fullName ? fullName : _miningKey;
|
||||
}
|
||||
|
||||
isValidaVote = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let isValidVote = await contractsStore[votingType].isValidVote(id, contractsStore.votingKey);
|
||||
return isValidVote;
|
||||
}
|
||||
|
||||
isActive = async () => {
|
||||
const { contractsStore, id, votingType } = this.props;
|
||||
let isActive = await contractsStore[votingType].isActive(id);
|
||||
return isActive;
|
||||
}
|
||||
|
||||
vote = async ({choice}) => {
|
||||
const { commonStore, contractsStore, id, votingType } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isValidVote = await this.isValidaVote();
|
||||
if (!isValidVote) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_VOTE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore[votingType].vote(id, choice, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.VOTED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
finalize = async (e) => {
|
||||
const { commonStore, contractsStore, id, votingType } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
if (this.isFinalized) {
|
||||
swal("Warning!", constants.ALREADY_FINALIZED_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isActive = await this.isActive();
|
||||
if (isActive) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_FINALIZE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore[votingType].finalize(id, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.FINALIZED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.isFinalized = false;
|
||||
this.getTimes();
|
||||
this.getCreator();
|
||||
this.getTotalVoters();
|
||||
this.getProgress();
|
||||
this.getIsFinalized();
|
||||
}
|
||||
|
||||
hideCard = () => {
|
||||
let { commonStore } = this.props;
|
||||
let hideCard = commonStore.isActiveFilter && this.isFinalized;
|
||||
if (commonStore.searchTerm) {
|
||||
if (commonStore.searchTerm.length == 0) return hideCard;
|
||||
if (String(this.creator).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
} else {
|
||||
return hideCard;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
render () {
|
||||
let { contractsStore, ballotStore, votingType, children } = this.props;
|
||||
let ballotClass = this.hideCard() ? "ballots-i display-none" : "ballots-i";
|
||||
let threshold
|
||||
switch(votingType) {
|
||||
case "votingToChangeKeys":
|
||||
threshold = contractsStore.keysBallotThreshold
|
||||
break;
|
||||
case "votingToChangeMinThreshold":
|
||||
threshold = contractsStore.minThresholdBallotThreshold
|
||||
break;
|
||||
case "votingToChangeProxy":
|
||||
threshold = contractsStore.proxyBallotThreshold
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className={ballotClass}>
|
||||
<div className="ballots-about">
|
||||
<div className="ballots-about-i ballots-about-i_name">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Name</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-i--name">{this.creator}</p>
|
||||
<p className="ballots-i--created">{this.startTime}</p>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
<div className="ballots-about-i ballots-about-i_time">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Time</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-i--time">{this.timeToFinish}</p>
|
||||
<p className="ballots-i--to-close">To close</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-i-scale">
|
||||
<div className="ballots-i-scale-column">
|
||||
<button type="button" onClick={(e) => this.vote({choice: REJECT})} className="ballots-i--vote ballots-i--vote_no">No</button>
|
||||
<div className="vote-scale--container">
|
||||
<p className="vote-scale--value">No</p>
|
||||
<p className="vote-scale--votes">Votes: {this.votesAgainstNumber}</p>
|
||||
<p className="vote-scale--percentage">{this.votesAgainstPercents}%</p>
|
||||
<div className="vote-scale">
|
||||
<div className="vote-scale--fill vote-scale--fill_yes" style={{width: `${this.votesAgainstPercents}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-i-scale-column">
|
||||
<div className="vote-scale--container">
|
||||
<p className="vote-scale--value">Yes</p>
|
||||
<p className="vote-scale--votes">Votes: {this.votesForNumber}</p>
|
||||
<p className="vote-scale--percentage">{this.votesForPercents}%</p>
|
||||
<div className="vote-scale">
|
||||
<div className="vote-scale--fill vote-scale--fill_no" style={{width: `${this.votesForPercents}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onClick={(e) => this.vote({choice: ACCEPT})} className="ballots-i--vote ballots-i--vote_yes">Yes</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info">
|
||||
Minimum {threshold} from {contractsStore.validatorsLength} validators is required to pass the proposal
|
||||
</div>
|
||||
<hr />
|
||||
<div className="ballots-footer">
|
||||
<div className="ballots-footer-left">
|
||||
<button type="button" onClick={(e) => this.finalize(e)} className="ballots-footer-finalize">Finalize ballot</button>
|
||||
<p>{constants.CARD_FINALIZE_DESCRIPTION}</p>
|
||||
</div>
|
||||
<div type="button" className="ballots-i--vote ballots-i--vote_no">Proxy Ballot ID: {this.props.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,55 +1,16 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { observable, action, computed, autorun } from "mobx";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toAscii } from "../helpers";
|
||||
import { constants } from "../constants";
|
||||
import swal from 'sweetalert2';
|
||||
import { BallotCard } from './BallotCard';
|
||||
|
||||
const ACCEPT = 1;
|
||||
const REJECT = 2;
|
||||
@inject("commonStore", "contractsStore", "ballotStore", "routing")
|
||||
@observer
|
||||
export class BallotKeysCard extends React.Component {
|
||||
@observable startTime;
|
||||
@observable endTime;
|
||||
@observable timeToFinish;
|
||||
@observable affectedKey;
|
||||
@observable affectedKeyType;
|
||||
@observable affectedKeyTypeDisplayName;
|
||||
@observable ballotType;
|
||||
@observable ballotTypeDisplayName;
|
||||
@observable creator;
|
||||
@observable progress;
|
||||
@observable totalVoters;
|
||||
@observable isFinalized;
|
||||
@observable isFiltered;
|
||||
|
||||
@computed get votesForNumber() {
|
||||
let votes = (this.totalVoters + this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesForPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesForNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@computed get votesAgainstNumber() {
|
||||
let votes = (this.totalVoters - this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesAgainstPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesAgainstNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@action("Get ballotTypeDisplayName")
|
||||
getBallotTypeDisplayName(ballotType) {
|
||||
|
@ -89,39 +50,6 @@ export class BallotKeysCard extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
@action("Get start time of keys ballot")
|
||||
getStartTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let startTime = await contractsStore.votingToChangeKeys.getStartTime(id);
|
||||
this.startTime = moment.utc(startTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Get end time of keys ballot")
|
||||
getEndTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let endTime = await contractsStore.votingToChangeKeys.getEndTime(id);
|
||||
this.endTime = moment.utc(endTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Calculate time to finish")
|
||||
calcTimeToFinish = () => {
|
||||
const now = moment();
|
||||
const finish = moment.utc(this.endTime, 'DD/MM/YYYY h:mm:ss A');
|
||||
let ms = finish.diff(now);
|
||||
if (ms <= 0)
|
||||
return this.timeToFinish = moment(0, "h").format("HH") + ":" + moment(0, "m").format("mm") + ":" + moment(0, "s").format("ss");
|
||||
|
||||
let dur = moment.duration(ms);
|
||||
this.timeToFinish = Math.floor(dur.asHours()) + moment.utc(ms).format(":mm:ss");
|
||||
}
|
||||
|
||||
@action("Get times")
|
||||
getTimes = async () => {
|
||||
await this.getStartTime();
|
||||
await this.getEndTime();
|
||||
this.calcTimeToFinish();
|
||||
}
|
||||
|
||||
@action("Get ballot type of keys ballot")
|
||||
getBallotType = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
|
@ -146,125 +74,13 @@ export class BallotKeysCard extends React.Component {
|
|||
this.affectedKey = affectedKey;
|
||||
}
|
||||
|
||||
@action("Get creator")
|
||||
getCreator = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let votingState = await contractsStore.votingToChangeKeys.votingState(id);
|
||||
this.getValidatorFullname(votingState.creator);
|
||||
}
|
||||
|
||||
@action("Get validator full name")
|
||||
getValidatorFullname = async (_miningKey) => {
|
||||
const { contractsStore } = this.props;
|
||||
let validator = await contractsStore.validatorMetadata.validators(_miningKey);
|
||||
let firstName = toAscii(validator.firstName);
|
||||
let lastName = toAscii(validator.lastName);
|
||||
let fullName = `${firstName} ${lastName}`
|
||||
this.creator = fullName ? fullName : _miningKey;
|
||||
}
|
||||
|
||||
@action("Get total voters")
|
||||
getTotalVoters = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let totalVoters = await contractsStore.votingToChangeKeys.getTotalVoters(id);
|
||||
this.totalVoters = Number(totalVoters);
|
||||
}
|
||||
|
||||
@action("Get progress")
|
||||
getProgress = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let progress = await contractsStore.votingToChangeKeys.getProgress(id);
|
||||
this.progress = Number(progress);
|
||||
}
|
||||
|
||||
@action("Get isFinalized")
|
||||
getIsFinalized = async() => {
|
||||
const { contractsStore, id } = this.props;
|
||||
this.isFinalized = await contractsStore.votingToChangeKeys.getIsFinalized(id);
|
||||
}
|
||||
|
||||
isValidaVote = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isValidVote = await contractsStore.votingToChangeKeys.isValidVote(id, contractsStore.votingKey);
|
||||
return isValidVote;
|
||||
}
|
||||
|
||||
isActive = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isActive = await contractsStore.votingToChangeKeys.isActive(id);
|
||||
return isActive;
|
||||
}
|
||||
|
||||
vote = async ({choice}) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isValidVote = await this.isValidaVote();
|
||||
if (!isValidVote) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_VOTE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeKeys.vote(id, choice, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.VOTED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
finalize = async (e) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
if (this.isFinalized) {
|
||||
swal("Warning!", constants.ALREADY_FINALIZED_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isActive = await this.isActive();
|
||||
if (isActive) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_FINALIZE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeKeys.finalize(id, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.FINALIZED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.isFinalized = false;
|
||||
this.getTimes();
|
||||
this.getAffectedKey();
|
||||
this.getAffectedKeyType();
|
||||
this.getBallotType();
|
||||
this.getCreator();
|
||||
this.getTotalVoters();
|
||||
this.getProgress();
|
||||
this.getIsFinalized();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.interval = setInterval(() => {
|
||||
this.calcTimeToFinish()
|
||||
|
@ -283,7 +99,6 @@ export class BallotKeysCard extends React.Component {
|
|||
if (String(this.affectedKey).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
if (String(this.affectedKeyTypeDisplayName).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
if (String(this.ballotTypeDisplayName).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
if (String(this.creator).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
} else {
|
||||
return hideCard;
|
||||
}
|
||||
|
@ -292,90 +107,35 @@ export class BallotKeysCard extends React.Component {
|
|||
}
|
||||
|
||||
render () {
|
||||
let { contractsStore } = this.props;
|
||||
let { contractsStore, id } = this.props;
|
||||
let ballotClass = this.hideCard() ? "ballots-i display-none" : "ballots-i";
|
||||
return (
|
||||
<div className={ballotClass}>
|
||||
<div className="ballots-about">
|
||||
<div className="ballots-about-i ballots-about-i_name">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Name</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-i--name">{this.creator}</p>
|
||||
<p className="ballots-i--created">{this.startTime}</p>
|
||||
</div>
|
||||
<BallotCard votingType="votingToChangeKeys" id={id}>
|
||||
<div className="ballots-about-i ballots-about-i_action">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Action</p>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_action">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Action</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.ballotTypeDisplayName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_type">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Key type</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.affectedKeyTypeDisplayName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_mining-key">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Affected key</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.affectedKey}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_time">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Time</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-i--time">{this.timeToFinish}</p>
|
||||
<p className="ballots-i--to-close">To close</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.ballotTypeDisplayName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-i-scale">
|
||||
<div className="ballots-i-scale-column">
|
||||
<button type="button" onClick={() => this.vote({choice: REJECT})} className="ballots-i--vote ballots-i--vote_no">No</button>
|
||||
<div className="vote-scale--container">
|
||||
<p className="vote-scale--value">No</p>
|
||||
<p className="vote-scale--votes">Votes: {this.votesAgainstNumber}</p>
|
||||
<p className="vote-scale--percentage">{this.votesAgainstPercents}%</p>
|
||||
<div className="vote-scale">
|
||||
<div className="vote-scale--fill vote-scale--fill_yes" style={{width: `${this.votesAgainstPercents}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_type">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Key type</p>
|
||||
</div>
|
||||
<div className="ballots-i-scale-column">
|
||||
<div className="vote-scale--container">
|
||||
<p className="vote-scale--value">Yes</p>
|
||||
<p className="vote-scale--votes">Votes: {this.votesForNumber}</p>
|
||||
<p className="vote-scale--percentage">{this.votesForPercents}%</p>
|
||||
<div className="vote-scale">
|
||||
<div className="vote-scale--fill vote-scale--fill_no" style={{width: `${this.votesForPercents}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" onClick={() => this.vote({choice: ACCEPT})} className="ballots-i--vote ballots-i--vote_yes">Yes</button>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.affectedKeyTypeDisplayName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="info">
|
||||
Minimum {contractsStore.keysBallotThreshold} from {contractsStore.validatorsLength} validators is required to pass the proposal
|
||||
</div>
|
||||
<hr />
|
||||
<div className="ballots-footer">
|
||||
<div className="ballots-footer-left">
|
||||
<button type="button" onClick={(e) => this.finalize(e)} className="ballots-footer-finalize">Finalize ballot</button>
|
||||
<p>{constants.CARD_FINALIZE_DESCRIPTION}</p>
|
||||
<div className="ballots-about-i ballots-about-i_mining-key">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Affected key</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.affectedKey}</p>
|
||||
</div>
|
||||
<div type="button" className="ballots-i--vote ballots-i--vote_no">Keys Ballot ID: {this.props.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</BallotCard>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,78 +5,14 @@ import { inject, observer } from "mobx-react";
|
|||
import { toAscii } from "../helpers";
|
||||
import { constants } from "../constants";
|
||||
import swal from 'sweetalert2';
|
||||
import { BallotCard } from './BallotCard';
|
||||
|
||||
const ACCEPT = 1;
|
||||
const REJECT = 2;
|
||||
@inject("commonStore", "contractsStore", "routing")
|
||||
@observer
|
||||
export class BallotMinThresholdCard extends React.Component {
|
||||
@observable startTime;
|
||||
@observable endTime;
|
||||
@observable timeToFinish;
|
||||
@observable proposedValue;
|
||||
@observable creator;
|
||||
@observable progress;
|
||||
@observable totalVoters;
|
||||
|
||||
@computed get votesForNumber() {
|
||||
let votes = (this.totalVoters + this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesForPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesForNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@computed get votesAgainstNumber() {
|
||||
let votes = (this.totalVoters - this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesAgainstPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesAgainstNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@action("Get start time of min threshold ballot")
|
||||
getStartTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let startTime = await contractsStore.votingToChangeMinThreshold.getStartTime(id);
|
||||
this.startTime = moment.utc(startTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Get end time of min threshold ballot")
|
||||
getEndTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let endTime = await contractsStore.votingToChangeMinThreshold.getEndTime(id);
|
||||
this.endTime = moment.utc(endTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Calculate time to finish")
|
||||
calcTimeToFinish = () => {
|
||||
const now = moment();
|
||||
const finish = moment.utc(this.endTime, 'DD/MM/YYYY h:mm:ss A');
|
||||
let ms = finish.diff(now);
|
||||
if (ms <= 0)
|
||||
return this.timeToFinish = moment(0, "h").format("HH") + ":" + moment(0, "m").format("mm") + ":" + moment(0, "s").format("ss");
|
||||
|
||||
let dur = moment.duration(ms);
|
||||
this.timeToFinish = Math.floor(dur.asHours()) + moment.utc(ms).format(":mm:ss");
|
||||
}
|
||||
|
||||
@action("Get times")
|
||||
getTimes = async () => {
|
||||
await this.getStartTime();
|
||||
await this.getEndTime();
|
||||
this.calcTimeToFinish();
|
||||
}
|
||||
|
||||
@action("Get proposed value of min threshold ballot")
|
||||
getProposedValue = async () => {
|
||||
|
@ -85,121 +21,9 @@ export class BallotMinThresholdCard extends React.Component {
|
|||
this.proposedValue = proposedValue;
|
||||
}
|
||||
|
||||
@action("Get creator")
|
||||
getCreator = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let votingState = await contractsStore.votingToChangeMinThreshold.votingState(id);
|
||||
this.getValidatorFullname(votingState.creator);
|
||||
}
|
||||
|
||||
@action("Get validator full name")
|
||||
getValidatorFullname = async (_miningKey) => {
|
||||
const { contractsStore } = this.props;
|
||||
let validator = await contractsStore.validatorMetadata.validators(_miningKey);
|
||||
let firstName = toAscii(validator.firstName);
|
||||
let lastName = toAscii(validator.lastName);
|
||||
let fullName = `${firstName} ${lastName}`
|
||||
this.creator = fullName ? fullName : _miningKey;
|
||||
}
|
||||
|
||||
@action("Get total voters")
|
||||
getTotalVoters = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let totalVoters = await contractsStore.votingToChangeMinThreshold.getTotalVoters(id);
|
||||
this.totalVoters = Number(totalVoters);
|
||||
}
|
||||
|
||||
@action("Get progress")
|
||||
getProgress = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let progress = await contractsStore.votingToChangeMinThreshold.getProgress(id);
|
||||
this.progress = Number(progress);
|
||||
}
|
||||
|
||||
@action("Get isFinalized")
|
||||
getIsFinalized = async() => {
|
||||
const { contractsStore, id } = this.props;
|
||||
this.isFinalized = await contractsStore.votingToChangeMinThreshold.getIsFinalized(id);
|
||||
}
|
||||
|
||||
isValidaVote = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isValidVote = await contractsStore.votingToChangeMinThreshold.isValidVote(id, contractsStore.votingKey);
|
||||
return isValidVote;
|
||||
}
|
||||
|
||||
isActive = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isActive = await contractsStore.votingToChangeMinThreshold.isActive(id);
|
||||
return isActive;
|
||||
}
|
||||
|
||||
vote = async ({choice}) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isValidVote = await this.isValidaVote();
|
||||
if (!isValidVote) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_VOTE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeMinThreshold.vote(id, choice, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.VOTED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
finalize = async (e) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
if (this.isFinalized) {
|
||||
swal("Warning!", constants.ALREADY_FINALIZED_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isActive = await this.isActive();
|
||||
if (isActive) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_FINALIZE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeMinThreshold.finalize(id, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.FINALIZED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getTimes(this.props.id);
|
||||
this.getProposedValue(this.props.id);
|
||||
this.getCreator(this.props.id);
|
||||
this.getTotalVoters(this.props.id);
|
||||
this.getProgress(this.props.id);
|
||||
this.getIsFinalized(this.props.id);
|
||||
}
|
||||
|
||||
hideCard = () => {
|
||||
|
@ -217,10 +41,20 @@ export class BallotMinThresholdCard extends React.Component {
|
|||
}
|
||||
|
||||
render () {
|
||||
let { contractsStore } = this.props;
|
||||
let { contractsStore, id } = this.props;
|
||||
let ballotClass = this.hideCard() ? "ballots-i display-none" : "ballots-i";
|
||||
return (
|
||||
<div className={ballotClass}>
|
||||
<BallotCard votingType="votingToChangeMinThreshold" id={id}>
|
||||
<div className="ballots-about-i ballots-about-i_proposed-min-threshold">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Proposed min threshold</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.proposedValue}</p>
|
||||
</div>
|
||||
</div>
|
||||
</BallotCard>
|
||||
/*<div className={ballotClass}>
|
||||
<div className="ballots-about">
|
||||
<div className="ballots-about-i ballots-about-i_name">
|
||||
<div className="ballots-about-td">
|
||||
|
@ -284,7 +118,7 @@ export class BallotMinThresholdCard extends React.Component {
|
|||
</div>
|
||||
<div type="button" className="ballots-i--vote ballots-i--vote_no">Consensus Ballot ID: {this.props.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>*/
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,83 +1,13 @@
|
|||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import { observable, action, computed } from "mobx";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { toAscii } from "../helpers";
|
||||
import { constants } from "../constants";
|
||||
import swal from 'sweetalert2';
|
||||
import { BallotCard } from './BallotCard';
|
||||
|
||||
const ACCEPT = 1;
|
||||
const REJECT = 2;
|
||||
@inject("commonStore", "contractsStore", "ballotStore", "routing")
|
||||
@observer
|
||||
export class BallotProxyCard extends React.Component {
|
||||
@observable startTime;
|
||||
@observable endTime;
|
||||
@observable timeToFinish;
|
||||
@observable proposedAddress;
|
||||
@observable contractType;
|
||||
@observable creator;
|
||||
@observable progress;
|
||||
@observable totalVoters;
|
||||
|
||||
@computed get votesForNumber() {
|
||||
let votes = (this.totalVoters + this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesForPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesForNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@computed get votesAgainstNumber() {
|
||||
let votes = (this.totalVoters - this.progress) / 2;
|
||||
return votes;
|
||||
}
|
||||
|
||||
@computed get votesAgainstPercents() {
|
||||
if (this.totalVoters <= 0)
|
||||
return 0;
|
||||
|
||||
let votesPercents = Math.round(this.votesAgainstNumber / this.totalVoters * 100);
|
||||
return votesPercents;
|
||||
}
|
||||
|
||||
@action("Get start time of proxy ballot")
|
||||
getStartTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let startTime = await contractsStore.votingToChangeProxy.getStartTime(id);
|
||||
this.startTime = moment.utc(startTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Get end time of proxy ballot")
|
||||
getEndTime = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let endTime = await contractsStore.votingToChangeProxy.getEndTime(id);
|
||||
this.endTime = moment.utc(endTime * 1000).format('DD/MM/YYYY h:mm:ss A');
|
||||
}
|
||||
|
||||
@action("Calculate time to finish")
|
||||
calcTimeToFinish = () => {
|
||||
const now = moment();
|
||||
const finish = moment.utc(this.endTime, 'DD/MM/YYYY h:mm:ss A');
|
||||
let ms = finish.diff(now);
|
||||
if (ms <= 0)
|
||||
return this.timeToFinish = moment(0, "h").format("HH") + ":" + moment(0, "m").format("mm") + ":" + moment(0, "s").format("ss");
|
||||
|
||||
let dur = moment.duration(ms);
|
||||
this.timeToFinish = Math.floor(dur.asHours()) + moment.utc(ms).format(":mm:ss");
|
||||
}
|
||||
|
||||
@action("Get times")
|
||||
getTimes = async () => {
|
||||
await this.getStartTime();
|
||||
await this.getEndTime();
|
||||
this.calcTimeToFinish();
|
||||
}
|
||||
|
||||
@action("Get proposed address of proxy ballot")
|
||||
getProposedAddress = async () => {
|
||||
|
@ -93,122 +23,10 @@ export class BallotProxyCard extends React.Component {
|
|||
this.contractType = contractType;
|
||||
}
|
||||
|
||||
@action("Get creator")
|
||||
getCreator = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let votingState = await contractsStore.votingToChangeProxy.votingState(id);
|
||||
this.getValidatorFullname(votingState.creator);
|
||||
}
|
||||
|
||||
@action("Get validator full name")
|
||||
getValidatorFullname = async (_miningKey) => {
|
||||
const { contractsStore } = this.props;
|
||||
let validator = await contractsStore.validatorMetadata.validators(_miningKey);
|
||||
let firstName = toAscii(validator.firstName);
|
||||
let lastName = toAscii(validator.lastName);
|
||||
let fullName = `${firstName} ${lastName}`
|
||||
this.creator = fullName ? fullName : _miningKey;
|
||||
}
|
||||
|
||||
@action("Get total voters")
|
||||
getTotalVoters = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let totalVoters = await contractsStore.votingToChangeProxy.getTotalVoters(id);
|
||||
this.totalVoters = Number(totalVoters);
|
||||
}
|
||||
|
||||
@action("Get progress")
|
||||
getProgress = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let progress = await contractsStore.votingToChangeProxy.getProgress(id);
|
||||
this.progress = Number(progress);
|
||||
}
|
||||
|
||||
@action("Get isFinalized")
|
||||
getIsFinalized = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
this.isFinalized = await contractsStore.votingToChangeProxy.getIsFinalized(id);
|
||||
}
|
||||
|
||||
isValidaVote = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isValidVote = await contractsStore.votingToChangeProxy.isValidVote(id, contractsStore.votingKey);
|
||||
return isValidVote;
|
||||
}
|
||||
|
||||
isActive = async () => {
|
||||
const { contractsStore, id } = this.props;
|
||||
let isActive = await contractsStore.votingToChangeProxy.isActive(id);
|
||||
return isActive;
|
||||
}
|
||||
|
||||
vote = async ({choice}) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isValidVote = await this.isValidaVote();
|
||||
if (!isValidVote) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_VOTE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeProxy.vote(id, choice, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.VOTED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
finalize = async (e) => {
|
||||
const { commonStore, contractsStore, id } = this.props;
|
||||
const { push } = this.props.routing;
|
||||
if (!contractsStore.isValidVotingKey) {
|
||||
swal("Warning!", constants.INVALID_VOTING_KEY_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
if (this.isFinalized) {
|
||||
swal("Warning!", constants.ALREADY_FINALIZED_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
commonStore.showLoading();
|
||||
let isActive = await this.isActive();
|
||||
if (isActive) {
|
||||
commonStore.hideLoading();
|
||||
swal("Warning!", constants.INVALID_FINALIZE_MSG, "warning");
|
||||
return;
|
||||
}
|
||||
contractsStore.votingToChangeProxy.finalize(id, contractsStore.votingKey)
|
||||
.on("receipt", () => {
|
||||
commonStore.hideLoading();
|
||||
swal("Congratulations!", constants.FINALIZED_SUCCESS_MSG, "success").then((result) => {
|
||||
push(`${commonStore.rootPath}`);
|
||||
});
|
||||
})
|
||||
.on("error", (e) => {
|
||||
commonStore.hideLoading();
|
||||
swal("Error!", e.message, "error");
|
||||
});
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.getTimes();
|
||||
this.getProposedAddress();
|
||||
this.getContractType();
|
||||
this.getCreator();
|
||||
this.getTotalVoters();
|
||||
this.getProgress();
|
||||
this.getIsFinalized();
|
||||
}
|
||||
|
||||
hideCard = () => {
|
||||
|
@ -218,7 +36,6 @@ export class BallotProxyCard extends React.Component {
|
|||
if (commonStore.searchTerm.length == 0) return hideCard;
|
||||
if (String(this.proposedAddress).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
if (String(this.contractType).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
if (String(this.creator).toLowerCase().includes(commonStore.searchTerm)) return (hideCard && false);
|
||||
} else {
|
||||
return hideCard;
|
||||
}
|
||||
|
@ -227,10 +44,29 @@ export class BallotProxyCard extends React.Component {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { contractsStore, ballotStore } = this.props;
|
||||
const { contractsStore, ballotStore, id } = this.props;
|
||||
let ballotClass = this.hideCard() ? "ballots-i display-none" : "ballots-i";
|
||||
return (
|
||||
<div className={ballotClass}>
|
||||
<BallotCard votingType="votingToChangeProxy" id={id}>
|
||||
<div className="ballots-about-i ballots-about-i_contract-type">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Contract type</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{ballotStore.ProxyBallotType[this.contractType]}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ballots-about-i ballots-about-i_proposed-address">
|
||||
<div className="ballots-about-td">
|
||||
<p className="ballots-about-i--title">Proposed contract address</p>
|
||||
</div>
|
||||
<div className="ballots-about-td">
|
||||
<p>{this.proposedAddress}</p>
|
||||
</div>
|
||||
</div>
|
||||
</BallotCard>
|
||||
);
|
||||
/* <div className={ballotClass}>
|
||||
<div className="ballots-about">
|
||||
<div className="ballots-about-i ballots-about-i_name">
|
||||
<div className="ballots-about-td">
|
||||
|
@ -302,7 +138,6 @@ export class BallotProxyCard extends React.Component {
|
|||
</div>
|
||||
<div type="button" className="ballots-i--vote ballots-i--vote_no">Proxy Ballot ID: {this.props.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>*/
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue