TC-1 | Add supported tail calldata for vote
This commit is contained in:
parent
498e1908e1
commit
3cef4c4d5b
11 changed files with 517 additions and 35 deletions
122
components/ProposalCommentFormModal.vue
Normal file
122
components/ProposalCommentFormModal.vue
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div class="modal-card box box-modal">
|
||||
<header class="box-modal-header is-spaced">
|
||||
<div class="box-modal-title">{{ $t('proposalComment.modal-title', { id: proposal.id }) }}</div>
|
||||
<button type="button" class="delete" @click="$emit('close')" />
|
||||
</header>
|
||||
|
||||
<p class="detail" v-text="$t('proposalComment.modal-subtitle')" />
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12">
|
||||
<b-field>
|
||||
<template #label>
|
||||
{{ $t('proposalComment.form-contact') }}
|
||||
<b-tooltip
|
||||
:label="$t('proposalComment.form-contact-tooltip')"
|
||||
size="is-medium"
|
||||
position="is-top"
|
||||
multilined
|
||||
>
|
||||
<button class="button is-primary has-icon">
|
||||
<span class="icon icon-info"></span>
|
||||
</button>
|
||||
</b-tooltip>
|
||||
</template>
|
||||
|
||||
<b-input
|
||||
v-model.trim="form.contact"
|
||||
:maxlength="limit / 2"
|
||||
:has-counter="false"
|
||||
:placeholder="$t('proposalComment.form-contact-placeholder')"
|
||||
/>
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="column is-12">
|
||||
<b-field
|
||||
:message="fields.message ? '' : $t('proposalComment.form-message-required')"
|
||||
:type="{ 'is-warning': !fields.message && !support }"
|
||||
:label="$t('proposalComment.form-message')"
|
||||
>
|
||||
<b-input
|
||||
v-model="form.message"
|
||||
:maxlength="limit"
|
||||
type="textarea"
|
||||
:placeholder="
|
||||
support
|
||||
? $t('proposalComment.form-message-opt-placeholder')
|
||||
: $t('proposalComment.form-message-placeholder')
|
||||
"
|
||||
/>
|
||||
</b-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-button v-if="support" type="is-primary" icon-left="check" outlined @click="onCastVote(true)">
|
||||
{{ $t('for') }}
|
||||
</b-button>
|
||||
|
||||
<b-button v-else type="is-danger" icon-left="close" outlined @click="onCastVote(false)">
|
||||
{{ $t('against') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const MESSAGE_LIMIT = 100
|
||||
|
||||
export default {
|
||||
props: {
|
||||
support: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
proposal: {
|
||||
type: Object,
|
||||
required: true,
|
||||
validator: (prop) => 'id' in prop
|
||||
}
|
||||
},
|
||||
data: () => ({
|
||||
limit: MESSAGE_LIMIT,
|
||||
fields: {
|
||||
contact: true,
|
||||
message: true
|
||||
},
|
||||
form: {
|
||||
contact: '',
|
||||
message: ''
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
validate() {
|
||||
const { form, fields, support } = this
|
||||
fields.contact = form.contact.length <= this.limit
|
||||
|
||||
fields.message = support
|
||||
? form.message.length <= this.limit
|
||||
: form.message.length > 2 && form.message.length <= this.limit
|
||||
|
||||
return fields.contact && fields.message
|
||||
},
|
||||
onCastVote() {
|
||||
const isValid = this.validate()
|
||||
|
||||
if (isValid) {
|
||||
this.$emit('castVote', this.form)
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-modal {
|
||||
overflow: initial !important;
|
||||
}
|
||||
|
||||
.detail {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -6,6 +6,14 @@
|
|||
<div class="description">
|
||||
<p>{{ data.description }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ProposalCommentsSkeleton
|
||||
v-if="isFetchingProposalComments"
|
||||
:size="proposalComments.length ? 1 : 3"
|
||||
/>
|
||||
<ProposalComment v-for="item in proposalComments" :key="item.id" v-bind="item" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-5-tablet is-4-desktop">
|
||||
<div v-if="data.status === 'active'" class="proposal-block">
|
||||
|
|
@ -21,18 +29,18 @@
|
|||
<b-button
|
||||
:disabled="readyForAction"
|
||||
type="is-primary"
|
||||
:icon-left="isFetchingBalances ? '' : 'check'"
|
||||
:icon-left="isFetchingBalances || isSaveProposal ? '' : 'check'"
|
||||
outlined
|
||||
:loading="isFetchingBalances"
|
||||
:loading="isFetchingBalances || isSaveProposal"
|
||||
@click="onCastVote(true)"
|
||||
>{{ $t('for') }}</b-button
|
||||
>
|
||||
<b-button
|
||||
:disabled="readyForAction"
|
||||
type="is-danger"
|
||||
:icon-left="isFetchingBalances ? '' : 'close'"
|
||||
:icon-left="isFetchingBalances || isSaveProposal ? '' : 'close'"
|
||||
outlined
|
||||
:loading="isFetchingBalances"
|
||||
:loading="isFetchingBalances || isSaveProposal"
|
||||
@click="onCastVote(false)"
|
||||
>{{ $t('against') }}</b-button
|
||||
>
|
||||
|
|
@ -160,11 +168,17 @@
|
|||
<script>
|
||||
import { mapState, mapActions, mapGetters } from 'vuex'
|
||||
import quorum from './mixins/quorum'
|
||||
import ProposalCommentsSkeleton from './ProposalCommentsSkeleton.vue'
|
||||
import ProposalComment from './ProposalComment.vue'
|
||||
import NumberFormat from '@/components/NumberFormat'
|
||||
import ProposalCommentFormModal from '@/components/ProposalCommentFormModal.vue'
|
||||
|
||||
const { toBN, fromWei, toWei } = require('web3-utils')
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProposalCommentsSkeleton,
|
||||
ProposalComment,
|
||||
NumberFormat
|
||||
},
|
||||
mixins: [quorum],
|
||||
|
|
@ -182,7 +196,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('governance/gov', ['proposals', 'voterReceipts']),
|
||||
...mapState('governance/gov', ['proposals', 'voterReceipts', 'proposalComments', 'isSaveProposal']),
|
||||
...mapState('metamask', ['ethAccount', 'isInitialized']),
|
||||
...mapGetters('txHashKeeper', ['addressExplorerUrl']),
|
||||
...mapGetters('metamask', ['networkConfig']),
|
||||
|
|
@ -191,6 +205,7 @@ export default {
|
|||
'constants',
|
||||
'votingPeriod',
|
||||
'isFetchingBalances',
|
||||
'isFetchingProposalComments',
|
||||
'isEnabledGovernance'
|
||||
]),
|
||||
readyForAction() {
|
||||
|
|
@ -224,7 +239,9 @@ export default {
|
|||
isInitialized: {
|
||||
handler(isInitialized) {
|
||||
if (isInitialized && this.isEnabledGovernance) {
|
||||
this.fetchReceipt({ id: this.data.id })
|
||||
const { id } = this.data
|
||||
this.fetchReceipt({ id })
|
||||
this.fetchProposalComments(this.data)
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
|
|
@ -265,7 +282,13 @@ export default {
|
|||
clearTimeout(this.timeId)
|
||||
},
|
||||
methods: {
|
||||
...mapActions('governance/gov', ['castVote', 'executeProposal', 'fetchReceipt', 'fetchProposals']),
|
||||
...mapActions('governance/gov', [
|
||||
'castVote',
|
||||
'executeProposal',
|
||||
'fetchReceipt',
|
||||
'fetchProposals',
|
||||
'fetchProposalComments'
|
||||
]),
|
||||
getStatusType(status) {
|
||||
let statusType = ''
|
||||
switch (status) {
|
||||
|
|
@ -299,7 +322,24 @@ export default {
|
|||
.toNumber()
|
||||
},
|
||||
onCastVote(support) {
|
||||
this.castVote({ id: this.data.id, support })
|
||||
const { id } = this.data
|
||||
|
||||
this.$buefy.modal.open({
|
||||
parent: this,
|
||||
component: ProposalCommentFormModal,
|
||||
hasModalCard: true,
|
||||
width: 440,
|
||||
customClass: 'is-pinned',
|
||||
props: {
|
||||
support,
|
||||
proposal: this.data
|
||||
},
|
||||
events: {
|
||||
castVote: ({ contact, message }) => {
|
||||
this.castVote({ id, support, contact, message })
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onExecute() {
|
||||
this.executeProposal({ id: this.data.id })
|
||||
|
|
|
|||
122
components/governance/ProposalComment.vue
Normal file
122
components/governance/ProposalComment.vue
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div class="proposals-box">
|
||||
<div class="columns is-gapless">
|
||||
<div class="column proposals-box--tags">
|
||||
<div
|
||||
class="tag"
|
||||
:class="{
|
||||
'proposals-box--revote': revote,
|
||||
'is-primary': support,
|
||||
'is-danger': !support
|
||||
}"
|
||||
>
|
||||
<span><number-format :value="votes" /> TORN</span>
|
||||
</div>
|
||||
|
||||
<b-tooltip v-if="delegator" :label="delegator" position="is-top">
|
||||
<div class="tag proposals-box--id">{{ $t('delegated') }}</div>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip :label="voter" position="is-top">
|
||||
<div class="tag proposals-box--id">{{ shortVoter }}</div>
|
||||
</b-tooltip>
|
||||
</div>
|
||||
<div class="column is-narrow proposals-box--date">
|
||||
<div class="date">
|
||||
<span>{{ $t('date') }}:</span> {{ date }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span v-if="contact" class="proposals-box--title">{{ contact }}</span>
|
||||
<div v-if="message" class="proposals-box--info" v-text="message" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { sliceAddress } from '@/utils'
|
||||
import NumberFormat from '@/components/NumberFormat'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NumberFormat
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
contact: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
support: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
timestamp: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
votes: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
voter: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
revote: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
delegator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data: (vm) => ({
|
||||
shortVoter: sliceAddress(vm.voter),
|
||||
date: [vm.$moment.unix(vm.timestamp).format('l'), vm.$moment.unix(vm.timestamp).format('hh:mm')].join(' ')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.proposals-box {
|
||||
cursor: default;
|
||||
|
||||
.tag {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, auto));
|
||||
display: grid;
|
||||
grid-row-gap: 0.714rem;
|
||||
grid-column-gap: 0.714rem;
|
||||
}
|
||||
|
||||
&--date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--title {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&--title,
|
||||
&--info {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
&--revote {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
components/governance/ProposalCommentsSkeleton.vue
Normal file
27
components/governance/ProposalCommentsSkeleton.vue
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-for="index in size" :key="index" class="proposals-box">
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-8-tablet is-9-desktop">
|
||||
<div class="proposals-box--title">
|
||||
<b-skeleton height="21" width="210" />
|
||||
</div>
|
||||
<div class="proposals-box--info">
|
||||
<b-skeleton height="21" width="260" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
default: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
<b-skeleton width="60%"></b-skeleton>
|
||||
<b-skeleton width="60%"></b-skeleton>
|
||||
</div>
|
||||
<ProposalCommentsSkeleton />
|
||||
</div>
|
||||
<div class="column is-5-tablet is-4-desktop">
|
||||
<div class="proposal-block">
|
||||
|
|
@ -77,3 +78,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ProposalCommentsSkeleton from './ProposalCommentsSkeleton.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProposalCommentsSkeleton
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="proposals-box" @click="onClick">
|
||||
<div class="proposals-box is-link" @click="onClick">
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-8-tablet is-9-desktop">
|
||||
<div class="title">
|
||||
<div class="proposals-box--title">
|
||||
{{ data.title }}
|
||||
</div>
|
||||
<div class="proposals-box--info">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in emptyArray" :key="index" class="proposals-box">
|
||||
<div v-for="index in size" :key="index" class="proposals-box">
|
||||
<div class="columns is-gapless">
|
||||
<div class="column is-8-tablet is-9-desktop">
|
||||
<div class="title">
|
||||
<div class="proposals-box--title">
|
||||
<b-skeleton height="28" width="210"></b-skeleton>
|
||||
</div>
|
||||
<div class="proposals-box--info">
|
||||
|
|
@ -39,17 +39,11 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
default: 5
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
emptyArray: Array(this.size).fill('')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue