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

492 lines
16 KiB
React
Raw Normal View History

2018-07-12 05:40:39 -07:00
import React from 'react'
import moment from 'moment'
import { observable, action, computed } from 'mobx'
import { inject, observer } from 'mobx-react'
import { messages } from '../messages'
import swal from 'sweetalert2'
2018-01-11 10:49:08 -08:00
2018-07-12 05:40:39 -07:00
const ACCEPT = 1
const REJECT = 2
const USDateTimeFormat = 'MM/DD/YYYY h:mm:ss A'
2018-07-12 05:40:39 -07:00
const zeroTimeTo = '00:00'
2018-07-12 05:40:39 -07:00
@inject('commonStore', 'contractsStore', 'routing', 'ballotsStore')
2018-01-11 10:49:08 -08:00
@observer
export class BallotCard extends React.Component {
2018-07-12 05:40:39 -07: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'
}
@observable creatorMiningKey
@observable creator
@observable progress
@observable totalVoters
@observable isFinalized
@observable canBeFinalized
@observable hasAlreadyVoted
@observable memo
@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() {
if (this.isFinalized) {
return ''
2018-01-15 05:47:34 -08:00
}
2018-07-12 05:40:39 -07:00
let description = 'Finalization is available after ballot time is finished'
if (this.canBeFinalized !== null) {
description += ' or all validators are voted'
}
2018-07-12 05:40:39 -07:00
return description
}
@computed
get votesForNumber() {
let votes = (this.totalVoters + this.progress) / 2
if (isNaN(votes)) votes = 0
return votes
}
@computed
get votesForPercents() {
if (this.totalVoters <= 0) {
return 0
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-07-12 05:40:39 -07:00
let votesPercents = Math.round((this.votesForNumber / this.totalVoters) * 100)
if (isNaN(votesPercents)) votesPercents = 0
return votesPercents
}
@computed
get votesAgainstNumber() {
let votes = (this.totalVoters - this.progress) / 2
if (isNaN(votes)) votes = 0
return votes
}
@computed
get votesAgainstPercents() {
if (this.totalVoters <= 0) {
return 0
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-07-12 05:40:39 -07:00
let votesPercents = Math.round((this.votesAgainstNumber / this.totalVoters) * 100)
if (isNaN(votesPercents)) votesPercents = 0
return votesPercents
}
@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 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-07-12 05:40:39 -07: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)
}
2018-07-12 05:40:39 -07:00
this.timeToFinish.val = 0
this.timeToFinish.displayValue = zeroTimeTo
return (this.timeTo = this.timeToFinish)
}
formatMs(ms, format) {
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
}
@action('validator has already voted')
getHasAlreadyVoted = async () => {
const { contractsStore, id, votingType } = this.props
let _hasAlreadyVoted = false
try {
_hasAlreadyVoted = await this.getContract(contractsStore, votingType).hasAlreadyVoted(
id,
contractsStore.votingKey
)
} catch (e) {
console.log(e.message)
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
this.hasAlreadyVoted = _hasAlreadyVoted
}
isValidVote = async () => {
const { contractsStore, id, votingType } = this.props
let _isValidVote
try {
_isValidVote = await this.getContract(contractsStore, votingType).isValidVote(id, contractsStore.votingKey)
} catch (e) {
console.log(e.message)
2018-03-22 06:33:01 -07:00
}
2018-07-12 05:40:39 -07:00
return _isValidVote
}
isActive = async () => {
const { contractsStore, id, votingType } = this.props
let _isActive = await this.repeatGetProperty(contractsStore, votingType, id, 'isActive', 0)
return _isActive
}
canBeFinalizedNow = async () => {
const { contractsStore, id, votingType } = this.props
let _canBeFinalizedNow = await this.repeatGetProperty(contractsStore, votingType, id, 'canBeFinalizedNow', 0)
this.canBeFinalized = _canBeFinalizedNow
}
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
}
2018-07-12 05:40:39 -07:00
const { commonStore, contractsStore, id, votingType, ballotsStore } = this.props
const { push } = this.props.routing
if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
commonStore.showLoading()
let isValidVote = await this.isValidVote()
if (!isValidVote) {
commonStore.hideLoading()
swal('Warning!', messages.INVALID_VOTE_MSG, 'warning')
return
}
2018-07-12 05:40:39 -07:00
const contract = this.getContract(contractsStore, votingType)
contract
.vote(id, choice, contractsStore.votingKey)
.on('receipt', async tx => {
commonStore.hideLoading()
if (tx.status === true || tx.status === '0x1') {
const ballotInfo = await contract.getBallotInfo(id, contractsStore.votingKey)
this.totalVoters = Number(ballotInfo.totalVoters)
this.progress = Number(ballotInfo.progress)
this.isFinalized = Boolean(ballotInfo.isFinalized)
if (ballotInfo.hasOwnProperty('canBeFinalizedNow')) {
this.canBeFinalized = Boolean(ballotInfo.canBeFinalizedNow)
} else {
await this.canBeFinalizedNow()
}
this.hasAlreadyVoted = true
ballotsStore.ballotCards[this.props.pos].props.votingState.totalVoters = this.totalVoters
ballotsStore.ballotCards[this.props.pos].props.votingState.progress = this.progress
ballotsStore.ballotCards[this.props.pos].props.votingState.isFinalized = this.isFinalized
ballotsStore.ballotCards[this.props.pos].props.votingState.canBeFinalized = this.canBeFinalized
ballotsStore.ballotCards[this.props.pos].props.votingState.hasAlreadyVoted = this.hasAlreadyVoted
swal('Congratulations!', messages.VOTED_SUCCESS_MSG, 'success').then(result => {
push(`${commonStore.rootPath}`)
})
} else {
swal('Warning!', messages.VOTE_FAILED_TX, 'warning')
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
})
.on('error', e => {
commonStore.hideLoading()
swal('Error!', e.message, 'error')
})
}
finalize = async e => {
if (this.isFinalized) {
return
2018-01-12 10:44:16 -08:00
}
2018-01-11 10:49:08 -08:00
2018-07-12 05:40:39 -07:00
if (this.timeToStart.val > 0) {
swal('Warning!', messages.ballotIsNotActiveMsg(this.timeTo.displayValue), 'warning')
return
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
const { commonStore, contractsStore, id, votingType, ballotsStore } = this.props
const { push } = this.props.routing
if (!contractsStore.isValidVotingKey) {
swal('Warning!', messages.invalidVotingKeyMsg(contractsStore.votingKey), 'warning')
return
}
2018-07-12 05:40:39 -07:00
if (this.isFinalized) {
swal('Warning!', messages.ALREADY_FINALIZED_MSG, 'warning')
return
2018-01-12 12:00:26 -08:00
}
2018-07-12 05:40:39 -07:00
commonStore.showLoading()
await this.canBeFinalizedNow()
let _canBeFinalized = this.canBeFinalized
if (_canBeFinalized === null) {
console.log('canBeFinalizedNow is not existed')
_canBeFinalized = !(await this.isActive())
}
2018-07-12 05:40:39 -07:00
if (!_canBeFinalized) {
commonStore.hideLoading()
swal('Warning!', messages.INVALID_FINALIZE_MSG, 'warning')
return
}
this.getContract(contractsStore, votingType)
.finalize(id, contractsStore.votingKey)
.on('receipt', tx => {
commonStore.hideLoading()
if (tx.status === true || tx.status === '0x1') {
this.isFinalized = true
ballotsStore.ballotCards[this.props.pos].props.votingState.isFinalized = this.isFinalized
if (this.canBeFinalized !== null) {
this.canBeFinalized = false
ballotsStore.ballotCards[this.props.pos].props.votingState.canBeFinalized = this.canBeFinalized
}
swal('Congratulations!', messages.FINALIZED_SUCCESS_MSG, 'success').then(result => {
push(`${commonStore.rootPath}`)
})
} else {
2018-07-12 05:40:39 -07:00
swal('Warning!', messages.FINALIZE_FAILED_TX, 'warning')
}
2018-07-12 05:40:39 -07:00
})
.on('error', e => {
commonStore.hideLoading()
swal('Error!', e.message, 'error')
})
}
repeatGetProperty = async (contractsStore, contractType, id, methodID, tryID) => {
try {
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
}
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
}
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 10:44:16 -08:00
}
2018-07-12 05:40:39 -07: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
}
2018-07-12 05:40:39 -07:00
}
constructor(props) {
super(props)
const { votingState } = this.props
// getTimes
this.startTime = moment.utc(votingState.startTime * 1000).format(USDateTimeFormat)
this.endTime = moment.utc(votingState.endTime * 1000).format(USDateTimeFormat)
this.calcTimeTo()
// getCreator
this.creator = votingState.creator
this.creatorMiningKey = votingState.creatorMiningKey
// getTotalVoters
this.totalVoters = Number(votingState.totalVoters)
// getProgress
this.progress = Number(votingState.progress)
// getIsFinalized
this.isFinalized = votingState.isFinalized
// canBeFinalizedNow
this.canBeFinalized = votingState.hasOwnProperty('canBeFinalizedNow') ? votingState.canBeFinalizedNow : null
// getMemo
this.memo = votingState.memo
// hasAlreadyVoted
if (votingState.hasOwnProperty('hasAlreadyVoted')) {
this.hasAlreadyVoted = votingState.hasAlreadyVoted
} else {
this.getHasAlreadyVoted()
2018-01-12 10:44:16 -08:00
}
2018-07-12 05:40:39 -07:00
}
componentDidMount() {
this.interval = setInterval(() => {
this.calcTimeTo()
}, 1000)
}
componentWillUnmount() {
window.clearInterval(this.interval)
}
showCard = () => {
let { commonStore } = this.props
let checkToFinalizeFilter = commonStore.isToFinalizeFilter
? !this.isFinalized && (this.timeToFinish.val === 0 || this.canBeFinalized) && this.timeToStart.val === 0
: true
let show = commonStore.isActiveFilter ? !this.isFinalized : checkToFinalizeFilter
return show
}
typeName(type) {
switch (type) {
case 'votingToChangeMinThreshold':
return 'Consensus'
case 'votingToChangeKeys':
return 'Keys'
case 'votingToChangeProxy':
return 'Proxy'
default:
return ''
2018-01-19 22:47:00 -08:00
}
2018-07-12 05:40:39 -07:00
}
render() {
let { contractsStore, votingType, children } = this.props
let ballotClass = this.showCard()
? this.isFinalized
? 'ballots-i'
: 'ballots-i ballots-i-not-finalized'
: 'ballots-i display-none'
let voteScaleClass = this.isFinalized ? 'vote-scale' : 'vote-scale vote-scale-not-finalized'
let hasAlreadyVotedLabel = (
<div className="ballots-i--vote ballots-i--vote-label ballots-i--vote-label-right ballots-i--vote_no">
You already voted
</div>
)
let showHasAlreadyVotedLabel = this.hasAlreadyVoted ? hasAlreadyVotedLabel : ''
const threshold = this.getThreshold(contractsStore, votingType)
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-11 10:49:08 -08:00
</div>
2018-07-12 05:40:39 -07:00
<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-11 10:49:08 -08:00
</div>
2018-07-12 05:40:39 -07:00
<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>
2018-07-12 05:40:39 -07:00
</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={voteScaleClass}>
<div
className="vote-scale--fill vote-scale--fill_yes"
style={{ width: `${this.votesAgainstPercents}%` }}
/>
</div>
</div>
2018-07-12 05:40:39 -07:00
</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={voteScaleClass}>
<div className="vote-scale--fill vote-scale--fill_no" style={{ width: `${this.votesForPercents}%` }} />
2018-01-11 10:49:08 -08:00
</div>
</div>
2018-07-12 05:40:39 -07:00
<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>
2018-07-12 05:40:39 -07:00
</div>
<div className="info">
Minimum {threshold} from {contractsStore.validatorsLength} validators are required to pass the proposal
</div>
<div className="info">{this.memo}</div>
<hr />
<div className="ballots-footer">
<div className="ballots-footer-left">
<button type="button" onClick={e => this.finalize(e)} className={this.finalizeButtonClass}>
{this.finalizeButtonDisplayName}
</button>
<p>{this.finalizeDescription}</p>
</div>
{showHasAlreadyVotedLabel}
<div className="ballots-i--vote ballots-i--vote-label ballots-i--vote_no">
{this.typeName(votingType)} Ballot ID: {this.props.id}
</div>
</div>
</div>
)
}
}