poa-dapps-voting/src/components/BallotCard.jsx

550 lines
20 KiB
React
Raw Normal View History

2018-01-12 10:44:16 -08:00
import React from "react";
import moment from "moment";
2018-01-12 08:14:34 -08:00
import { observable, action, computed } from "mobx";
2018-01-11 10:49:08 -08:00
import { inject, observer } from "mobx-react";
import { messages } from "../messages";
2018-01-12 10:44:16 -08:00
import swal from "sweetalert2";
2018-01-11 10:49:08 -08:00
const ACCEPT = 1;
const REJECT = 2;
const USDateTimeFormat = "MM/DD/YYYY h:mm:ss A";
const zeroTimeTo = "00:00";
2018-01-11 10:49:08 -08:00
@inject("commonStore", "contractsStore", "ballotStore", "routing")
@observer
export class BallotCard extends React.Component {
2018-01-12 10:44:16 -08:00
@observable startTime;
@observable endTime;
@observable timeTo = {};
@observable timeToStart = {
val: 0,
displayValue: zeroTimeTo,
title: "To start"
};
@observable timeToFinish = {
val: 0,
displayValue: zeroTimeTo,
title: "To close"
};
2018-03-20 06:50:56 -07:00
@observable creatorMiningKey;
2018-01-12 10:44:16 -08:00
@observable creator;
@observable progress;
@observable totalVoters;
@observable isFinalized;
@observable canBeFinalized;
2018-03-22 06:33:01 -07:00
@observable hasAlreadyVoted;
@observable memo;
2018-01-11 10:49:08 -08:00
2018-01-15 05:47:34 -08:00
@computed get finalizeButtonDisplayName() {
const displayName = this.isFinalized ? "Finalized" : "Finalize ballot";
return displayName;
}
@computed get finalizeButtonClass () {
const cls = this.isFinalized ? "ballots-footer-finalize ballots-footer-finalize-finalized" : "ballots-footer-finalize";
return cls;
}
@computed get finalizeDescription () {
2018-06-13 03:44:04 -07:00
if (this.isFinalized) {
return '';
}
let description = 'Finalization is available after ballot time is finished';
2018-06-19 02:56:54 -07:00
if (this.canBeFinalized !== null) {
2018-06-13 03:44:04 -07:00
description += ' or all validators are voted';
}
return description;
}
2018-01-12 10:44:16 -08:00
@computed get votesForNumber() {
let votes = (this.totalVoters + this.progress) / 2;
2018-03-20 14:57:28 -07:00
if (isNaN(votes))
votes = 0;
2018-01-12 10:44:16 -08:00
return votes;
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@computed get votesForPercents() {
2018-01-12 11:09:04 -08:00
if (this.totalVoters <= 0) {
2018-01-12 10:44:16 -08:00
return 0;
2018-01-12 11:09:04 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
let votesPercents = Math.round(this.votesForNumber / this.totalVoters * 100);
2018-03-20 14:57:28 -07:00
if (isNaN(votesPercents))
votesPercents = 0;
2018-01-12 10:44:16 -08:00
return votesPercents;
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@computed get votesAgainstNumber() {
let votes = (this.totalVoters - this.progress) / 2;
2018-03-20 14:57:28 -07:00
if (isNaN(votes))
votes = 0;
2018-01-12 10:44:16 -08:00
return votes;
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@computed get votesAgainstPercents() {
2018-01-12 11:09:04 -08:00
if (this.totalVoters <= 0) {
2018-01-12 10:44:16 -08:00
return 0;
2018-01-12 11:09:04 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
let votesPercents = Math.round(this.votesAgainstNumber / this.totalVoters * 100);
2018-03-20 14:57:28 -07:00
if (isNaN(votesPercents))
votesPercents = 0;
2018-01-12 10:44:16 -08:00
return votesPercents;
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get start time of keys ballot")
getStartTime = async () => {
const { contractsStore, id, votingType } = this.props;
2018-03-22 07:53:24 -07:00
let startTime = await this.repeatGetProperty(contractsStore, votingType, id, "getStartTime", 0);
this.startTime = moment.utc(startTime * 1000).format(USDateTimeFormat);
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get end time of keys ballot")
getEndTime = async () => {
const { contractsStore, id, votingType } = this.props;
let endTime = await this.repeatGetProperty(contractsStore, votingType, id, "getEndTime", 0);
this.endTime = moment.utc(endTime * 1000).format(USDateTimeFormat);
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
@action("Calculate time to start/finish")
calcTimeTo = () => {
const _now = moment();
const start = moment.utc(this.startTime, USDateTimeFormat);
const finish = moment.utc(this.endTime, USDateTimeFormat);
let msStart = start.diff(_now);
let msFinish = finish.diff(_now);
if (msStart > 0) {
this.timeToStart.val = msStart + 5000;
this.timeToStart.displayValue = this.formatMs(msStart, ":mm:ss");
return this.timeTo = this.timeToStart;
2018-01-12 11:09:04 -08:00
}
2018-01-11 10:49:08 -08:00
if (msFinish > 0) {
this.timeToStart.val = 0;
this.timeToFinish.val = msFinish;
this.timeToFinish.displayValue = this.formatMs(msFinish, ":mm:ss");
return this.timeTo = this.timeToFinish;
}
this.timeToFinish.val = 0;
this.timeToFinish.displayValue = zeroTimeTo;
return this.timeTo = this.timeToFinish;
}
formatMs (ms, format) {
2018-01-12 10:44:16 -08:00
let dur = moment.duration(ms);
let hours = Math.floor(dur.asHours());
hours = hours < 10 ? "0" + hours : hours;
let formattedMs = hours + moment.utc(ms).format(":mm:ss");
return formattedMs;
2018-01-12 10:44:16 -08:00
}
2018-03-22 03:04:05 -07:00
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get times")
getTimes = async () => {
await this.getStartTime();
await this.getEndTime();
this.calcTimeTo();
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get creator")
getCreator = async () => {
const { contractsStore, id, votingType } = this.props;
2018-05-08 05:53:24 -07:00
let creator = await this.repeatGetProperty(contractsStore, votingType, id, "getCreator", 0);
if (creator) {
this.getValidatorFullname(creator);
} else {
let votingState = await this.repeatGetProperty(contractsStore, votingType, id, "votingState", 0);
if (votingState) {
this.getValidatorFullname(votingState.creator);
}
2018-03-20 14:57:28 -07:00
}
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-06-14 01:05:35 -07:00
@action("Get votingState")
getVotingState = async () => {
const { contractsStore, id, votingType } = this.props;
2018-06-14 05:41:01 -07:00
let votingState = this.props.votingState;
if (!votingState) {
votingState = await this.repeatGetProperty(contractsStore, votingType, id, "votingState", 0);
}
2018-06-14 01:05:35 -07:00
if (votingState) {
// getTimes
this.startTime = moment.utc(votingState.startTime * 1000).format(USDateTimeFormat);
this.endTime = moment.utc(votingState.endTime * 1000).format(USDateTimeFormat);
this.calcTimeTo();
// getCreator
this.getValidatorFullname(votingState.creator);
// getTotalVoters
if (votingState.totalVoters) {
this.totalVoters = Number(votingState.totalVoters);
}
// getProgress
if (votingState.progress) {
this.progress = Number(votingState.progress);
}
// getIsFinalized
this.isFinalized = votingState.isFinalized;
// canBeFinalizedNow
this.canBeFinalized = votingState.hasOwnProperty('canBeFinalizedNow') ? votingState.canBeFinalizedNow : null;
2018-06-14 01:05:35 -07:00
// getMemo
this.memo = votingState.memo;
// hasAlreadyVoted
if (votingState.hasOwnProperty('hasAlreadyVoted')) {
this.hasAlreadyVoted = votingState.hasAlreadyVoted;
} else {
this.getHasAlreadyVoted();
}
2018-06-14 01:05:35 -07:00
} else {
this.getTimes();
const creator = await this.repeatGetProperty(contractsStore, votingType, id, "getCreator", 0);
if (creator) {
this.getValidatorFullname(creator);
}
this.getTotalVoters();
this.getProgress();
this.getIsFinalized();
this.canBeFinalizedNow();
2018-06-14 01:05:35 -07:00
this.getMemo();
this.getHasAlreadyVoted();
2018-06-14 01:05:35 -07:00
}
}
2018-01-12 10:44:16 -08:00
@action("Get progress")
getProgress = async () => {
const { contractsStore, id, votingType } = this.props;
let progress = await this.repeatGetProperty(contractsStore, votingType, id, "getProgress", 0);
2018-03-20 14:57:28 -07:00
if (progress) {
this.progress = Number(progress);
}
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get total voters")
getTotalVoters = async () => {
const { contractsStore, id, votingType } = this.props;
let totalVoters = await this.repeatGetProperty(contractsStore, votingType, id, "getTotalVoters", 0);
2018-03-20 14:57:28 -07:00
if (totalVoters) {
this.totalVoters = Number(totalVoters);
}
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get isFinalized")
getIsFinalized = async() => {
const { contractsStore, id, votingType } = this.props;
let isFinalized = await this.repeatGetProperty(contractsStore, votingType, id, "getIsFinalized", 0);
2018-03-20 14:57:28 -07:00
this.isFinalized = isFinalized;
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
@action("Get validator full name")
getValidatorFullname = async (_miningKey) => {
const { contractsStore } = this.props;
const miningKeyLowerCase = _miningKey.toLowerCase();
let fullName;
if (contractsStore.validatorsMetadata.hasOwnProperty(miningKeyLowerCase)) {
fullName = contractsStore.validatorsMetadata[miningKeyLowerCase].fullName;
2018-03-20 14:57:28 -07:00
}
2018-03-20 06:50:56 -07:00
this.creatorMiningKey = _miningKey;
2018-01-12 10:44:16 -08:00
this.creator = fullName ? fullName : _miningKey;
}
2018-01-11 10:49:08 -08:00
2018-03-22 06:33:01 -07:00
@action("validator has already voted")
getHasAlreadyVoted = async () => {
const { contractsStore, id, votingType } = this.props;
2018-03-22 07:53:24 -07:00
let _hasAlreadyVoted = false;
2018-03-22 06:33:01 -07:00
try {
_hasAlreadyVoted = await this.getContract(contractsStore, votingType).hasAlreadyVoted(id, contractsStore.votingKey);
} catch(e) {
console.log(e.message);
}
this.hasAlreadyVoted = _hasAlreadyVoted;
}
isValidVote = async () => {
2018-01-12 10:44:16 -08:00
const { contractsStore, id, votingType } = this.props;
let _isValidVote;
2018-03-20 14:57:28 -07:00
try {
_isValidVote = await this.getContract(contractsStore, votingType).isValidVote(id, contractsStore.votingKey);
2018-03-20 14:57:28 -07:00
} catch(e) {
console.log(e.message);
}
return _isValidVote;
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
isActive = async () => {
const { contractsStore, id, votingType } = this.props;
let _isActive = await this.repeatGetProperty(contractsStore, votingType, id, "isActive", 0);
return _isActive;
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
canBeFinalizedNow = async () => {
const { contractsStore, id, votingType } = this.props;
let _canBeFinalizedNow = await this.repeatGetProperty(contractsStore, votingType, id, "canBeFinalizedNow", 0);
this.canBeFinalized = _canBeFinalizedNow;
}
getMemo = async () => {
const { contractsStore, id, votingType } = this.props;
let memo = await this.repeatGetProperty(contractsStore, votingType, id, "getMemo", 0);
this.memo = memo;
return memo;
}
2018-01-12 10:44:16 -08:00
vote = async ({choice}) => {
if (this.timeToStart.val > 0) {
swal("Warning!", messages.ballotIsNotActiveMsg(this.timeTo.displayValue), "warning");
return;
}
2018-01-12 10:44:16 -08:00
const { commonStore, contractsStore, id, votingType } = this.props;
const { push } = this.props.routing;
if (!contractsStore.isValidVotingKey) {
swal("Warning!", messages.invalidVotingKeyMsg(contractsStore.votingKey), "warning");
2018-01-12 10:44:16 -08:00
return;
}
commonStore.showLoading();
let isValidVote = await this.isValidVote();
2018-01-12 10:44:16 -08:00
if (!isValidVote) {
commonStore.hideLoading();
swal("Warning!", messages.INVALID_VOTE_MSG, "warning");
2018-01-12 10:44:16 -08:00
return;
}
this.getContract(contractsStore, votingType).vote(id, choice, contractsStore.votingKey)
2018-01-12 10:44:16 -08:00
.on("receipt", () => {
commonStore.hideLoading();
swal("Congratulations!", messages.VOTED_SUCCESS_MSG, "success").then((result) => {
2018-01-12 10:44:16 -08:00
push(`${commonStore.rootPath}`);
});
})
.on("error", (e) => {
commonStore.hideLoading();
swal("Error!", e.message, "error");
});
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
finalize = async (e) => {
2018-03-22 03:04:05 -07:00
if (this.isFinalized) {
return;
}
if (this.timeToStart.val > 0) {
swal("Warning!", messages.ballotIsNotActiveMsg(this.timeTo.displayValue), "warning");
return;
}
2018-01-12 10:44:16 -08:00
const { commonStore, contractsStore, id, votingType } = this.props;
const { push } = this.props.routing;
if (!contractsStore.isValidVotingKey) {
swal("Warning!", messages.invalidVotingKeyMsg(contractsStore.votingKey), "warning");
2018-01-12 10:44:16 -08:00
return;
}
if (this.isFinalized) {
swal("Warning!", messages.ALREADY_FINALIZED_MSG, "warning");
2018-01-12 10:44:16 -08:00
return;
}
commonStore.showLoading();
await this.canBeFinalizedNow();
let _canBeFinalized = this.canBeFinalized;
if (_canBeFinalized === null) {
console.log('canBeFinalizedNow is not existed');
_canBeFinalized = !(await this.isActive());
}
if (!_canBeFinalized) {
2018-01-12 10:44:16 -08:00
commonStore.hideLoading();
swal("Warning!", messages.INVALID_FINALIZE_MSG, "warning");
2018-01-12 10:44:16 -08:00
return;
}
this.getContract(contractsStore, votingType).finalize(id, contractsStore.votingKey)
2018-01-12 10:44:16 -08:00
.on("receipt", () => {
commonStore.hideLoading();
swal("Congratulations!", messages.FINALIZED_SUCCESS_MSG, "success").then((result) => {
2018-01-12 10:44:16 -08:00
push(`${commonStore.rootPath}`);
});
})
.on("error", (e) => {
commonStore.hideLoading();
swal("Error!", e.message, "error");
});
}
2018-01-11 10:49:08 -08:00
repeatGetProperty = async (contractsStore, contractType, id, methodID, tryID) => {
try {
2018-06-13 03:44:04 -07:00
let val = await this.getContract(contractsStore, contractType)[methodID](id);
if (tryID > 0) {
console.log(`success from Try ${tryID + 1}`);
}
return val;
} catch(e) {
if (tryID < 10) {
console.log(`trying to repeat get value again... Try ${tryID + 1}`);
tryID++;
await setTimeout(async () => {
this.repeatGetProperty(contractsStore, contractType, id, methodID, tryID);
}, 1000)
} else {
return null;
}
}
}
getContract(contractsStore, contractType) {
switch(contractType) {
case "votingToChangeKeys":
return contractsStore.votingToChangeKeys;
case "votingToChangeMinThreshold":
return contractsStore.votingToChangeMinThreshold;
case "votingToChangeProxy":
return contractsStore.votingToChangeProxy;
case "validatorMetadata":
return contractsStore.validatorMetadata;
default:
return contractsStore.votingToChangeKeys;
2018-01-12 12:00:26 -08:00
}
}
getThreshold(contractsStore, votingType) {
switch(votingType) {
case "votingToChangeKeys":
return contractsStore.keysBallotThreshold;
case "votingToChangeMinThreshold":
return contractsStore.minThresholdBallotThreshold;
case "votingToChangeProxy":
return contractsStore.proxyBallotThreshold;
default:
return contractsStore.keysBallotThreshold;
}
}
2018-01-12 10:44:16 -08:00
constructor(props) {
super(props);
this.isFinalized = false;
2018-03-22 06:33:01 -07:00
this.hasAlreadyVoted = false;
2018-06-14 01:05:35 -07:00
this.getVotingState();
2018-01-12 10:44:16 -08:00
}
2018-01-12 08:14:34 -08:00
2018-01-12 10:44:16 -08:00
componentDidMount() {
this.interval = setInterval(() => {
this.calcTimeTo();
2018-01-12 11:09:04 -08:00
}, 1000);
2018-01-12 10:44:16 -08:00
}
2018-01-12 08:14:34 -08:00
2018-01-12 10:44:16 -08:00
componentWillUnmount() {
window.clearInterval(this.interval);
}
2018-01-12 08:14:34 -08:00
2018-01-12 10:44:16 -08:00
showCard = () => {
let { commonStore } = this.props;
let checkToFinalizeFilter = commonStore.isToFinalizeFilter ? !this.isFinalized && (this.timeToFinish.val == 0 || this.canBeFinalized) && this.timeToStart.val == 0 : true;
2018-03-21 09:39:18 -07:00
let show = commonStore.isActiveFilter ? !this.isFinalized : checkToFinalizeFilter;
2018-01-12 10:44:16 -08:00
return show;
}
2018-01-11 10:49:08 -08:00
2018-01-12 10:44:16 -08:00
isCreatorPattern = () => {
let { commonStore } = this.props;
if (commonStore.searchTerm) {
if (commonStore.searchTerm.length > 0) {
2018-03-20 06:50:56 -07:00
const _isCreatorPattern = String(this.creator).toLowerCase().includes(commonStore.searchTerm);
const _isCreatorMiningKeyPattern = String(this.creatorMiningKey).toLowerCase().includes(commonStore.searchTerm);
return _isCreatorPattern || _isCreatorMiningKeyPattern;
}
}
return true;
}
isMemoPattern = () => {
let { commonStore } = this.props;
if (commonStore.searchTerm) {
if (commonStore.searchTerm.length > 0) {
const _isMemoPattern = String(this.memo).toLowerCase().includes(commonStore.searchTerm);
return _isMemoPattern;
2018-01-12 10:44:16 -08:00
}
}
return true;
2018-01-11 10:49:08 -08:00
}
2018-01-12 10:44:16 -08:00
2018-01-19 22:47:00 -08:00
typeName(type){
switch(type) {
case "votingToChangeMinThreshold":
return "Consensus";
case "votingToChangeKeys":
return "Keys";
case "votingToChangeProxy":
return "Proxy";
default:
return "";
}
}
2018-01-12 10:44:16 -08:00
render () {
let { contractsStore, votingType, children, isSearchPattern } = this.props;
2018-03-21 09:05:44 -07:00
let isFromSearch = (this.isCreatorPattern() || this.isMemoPattern() || isSearchPattern);
let ballotClass = (this.showCard() && isFromSearch) ? this.isFinalized ? "ballots-i" : "ballots-i ballots-i-not-finalized" : "ballots-i display-none";
2018-03-22 06:33:01 -07:00
let voteScaleClass = this.isFinalized ? "vote-scale" : "vote-scale vote-scale-not-finalized";
2018-03-22 07:53:24 -07:00
let hasAlreadyVotedLabel = <div className="ballots-i--vote ballots-i--vote-label ballots-i--vote-label-right ballots-i--vote_no">You already voted</div>;
2018-03-22 06:33:01 -07:00
let showHasAlreadyVotedLabel = this.hasAlreadyVoted ? hasAlreadyVotedLabel : "";
2018-01-12 12:00:26 -08:00
const threshold = this.getThreshold(contractsStore, votingType);
2018-01-12 10:44:16 -08:00
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">Proposer</p>
2018-01-12 10:44:16 -08:00
</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">Ballot Time</p>
2018-01-12 10:44:16 -08:00
</div>
<div className="ballots-about-td">
<p className="ballots-i--time">{this.timeTo.displayValue}</p>
<p className="ballots-i--to-close">{this.timeTo.title}</p>
2018-01-12 10:44:16 -08:00
</div>
</div>
2018-01-11 10:49:08 -08:00
</div>
2018-01-12 10:44:16 -08:00
<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>
2018-03-22 06:33:01 -07:00
<div className={voteScaleClass}>
2018-01-12 10:44:16 -08:00
<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>
2018-03-22 06:33:01 -07:00
<div className={voteScaleClass}>
2018-01-12 10:44:16 -08:00
<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>
2018-01-11 10:49:08 -08:00
</div>
</div>
2018-01-12 10:44:16 -08:00
<div className="info">
Minimum {threshold} from {contractsStore.validatorsLength} validators are required to pass the proposal
2018-01-12 10:44:16 -08:00
</div>
<div className="info">
{this.memo}
</div>
2018-01-12 10:44:16 -08:00
<hr />
<div className="ballots-footer">
<div className="ballots-footer-left">
2018-01-15 05:47:34 -08:00
<button type="button" onClick={(e) => this.finalize(e)} className={this.finalizeButtonClass}>{this.finalizeButtonDisplayName}</button>
<p>{this.finalizeDescription}</p>
2018-01-11 10:49:08 -08:00
</div>
2018-03-22 06:33:01 -07:00
{showHasAlreadyVotedLabel}
<div className="ballots-i--vote ballots-i--vote-label ballots-i--vote_no">{this.typeName(votingType)} Ballot ID: {this.props.id}</div>
2018-01-11 10:49:08 -08:00
</div>
</div>
2018-01-12 10:44:16 -08:00
);
}
}