Feature: Batch voting #62

Closed
fsmanuel wants to merge 3 commits from feature/batch-voting into master
8 changed files with 103 additions and 33 deletions
+17 -7
View File
@@ -1,20 +1,30 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { inject } from '@ember/service';
import { empty } from '@ember/object/computed';
export default Component.extend({ export default Component.extend({
kredits: inject(),
tagName: 'ul', tagName: 'ul',
classNames: ['proposal-list'], classNames: ['proposal-list'],
selectedProposals: [],
submitButtonDisabled: empty('selectedProposals'),
actions: { actions: {
confirm() {
this.confirmProposals(this.selectedProposals)
.then(() => {
this.selectedProposals = [];
});
},
confirm(proposalId) { toggleSelect(proposalId, selected) {
if (this.contractInteractionEnabled) { if (selected) {
this.confirmProposal(proposalId); this.selectedProposals.removeObject(proposalId);
} else { } else {
window.alert('Only members can vote on proposals. Please ask someone to set you up.'); this.selectedProposals.addObject(proposalId);
} }
},
} }
}
}); });
@@ -0,0 +1,29 @@
import Component from '@ember/component';
import tag from 'ember-awesome-macros/tag';
import { computed } from '@ember/object';
import { reads } from '@ember/object/computed';
import { inject } from '@ember/service';
export default Component.extend({
kredits: inject(),
tagName: 'li',
classNames: ['proposal-list-item'],
attributeBindings: ['data-proposal-id', 'title'],
title: tag`(${'proposal.kind'}) ${'proposal.description'}`,
'data-proposal-id': reads('proposal.id'),
canBeVoted: computed('voterIds.[]', 'kredits.currentUser', function() {
let { isExecuted, voterIds } = this.proposal;
let { currentUser } = this.kredits;
voterIds = voterIds.map((id) => id.toString());
return currentUser.isCore && !isExecuted && !voterIds.includes(currentUser.id);
}),
selected: computed('selectedProposals.[]', function() {
return this.selectedProposals.includes(this.proposal.id);
})
});
@@ -0,0 +1,8 @@
<span class="category {{proposal.kind}}">♥</span>
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
for <span class="recipient">{{proposal.contributor.name}}</span>
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
{{#if canBeVoted}}
<button {{action toggleSelect proposal.id selected}} class="{{if selected 'selected' ''}}">+1</button>
{{/if}}
+13 -10
View File
@@ -1,12 +1,15 @@
{{#each proposals as |proposal|}} {{#each proposals as |proposal|}}
<li data-proposal-id={{proposal.id}} title="({{proposal.kind}}) {{proposal.description}}"> {{proposal-list/item
<span class="category {{proposal.kind}}">♥</span> proposal=proposal
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span> selectedProposals=selectedProposals
for <span class="recipient">{{proposal.contributor.name}}</span> toggleSelect=(action 'toggleSelect')
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span> }}
{{#unless proposal.isExecuted}}
<button {{action "confirm" proposal.id}}>+1</button>
{{/unless}}
</li>
{{/each}} {{/each}}
{{#if confirmProposals}}
<center>
<button disabled={{submitButtonDisabled}} {{action 'confirm'}}>
Submit selected
</button>
</center>
{{/if}}
+10 -2
View File
@@ -1,6 +1,7 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { alias, filter, filterBy, sort } from '@ember/object/computed'; import { alias, filter, filterBy, sort } from '@ember/object/computed';
import { inject as injectService } from '@ember/service'; import { inject as injectService } from '@ember/service';
import RSVP from 'rsvp';
export default Controller.extend({ export default Controller.extend({
kredits: injectService(), kredits: injectService(),
@@ -20,10 +21,17 @@ export default Controller.extend({
proposalsOpenSorted: sort('proposalsOpen', 'proposalsSorting'), proposalsOpenSorted: sort('proposalsOpen', 'proposalsSorting'),
actions: { actions: {
confirmProposal(proposalId) { confirmProposals(proposalIds) {
this.kredits.vote(proposalId).then(transaction => { if (this.kredits.currentUser.isCore) {
return this.kredits.batchVote(proposalIds)
.then((transaction) => {
window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash); window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash);
return transaction;
}); });
} else {
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
return RSVP.reject();
}
}, },
save(contributor) { save(contributor) {
+6 -7
View File
@@ -4,7 +4,7 @@ import RSVP from 'rsvp';
import Service from '@ember/service'; import Service from '@ember/service';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { alias, notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { isEmpty } from '@ember/utils'; import { isEmpty } from '@ember/utils';
import config from 'kredits-web/config/environment'; import config from 'kredits-web/config/environment';
@@ -16,8 +16,6 @@ export default Service.extend({
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded. currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
currentUser: null, currentUser: null,
currentUserIsContributor: notEmpty('currentUser'),
currentUserIsCore: alias('currentUser.isCore'),
hasAccounts: notEmpty('currentUserAccounts'), hasAccounts: notEmpty('currentUserAccounts'),
accountNeedsUnlock: computed('currentUserAccounts', function() { accountNeedsUnlock: computed('currentUserAccounts', function() {
return this.currentUserAccounts && isEmpty(this.currentUserAccounts); return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
@@ -68,7 +66,7 @@ export default Service.extend({
if (this.currentUserAccounts && this.currentUserAccounts.length > 0) { if (this.currentUserAccounts && this.currentUserAccounts.length > 0) {
this.getCurrentUser.then((contributorData) => { this.getCurrentUser.then((contributorData) => {
this.set('currentUser', contributorData); this.set('currentUser', Contributor.create(contributorData));
}); });
} }
return kredits; return kredits;
@@ -130,10 +128,11 @@ export default Service.extend({
}); });
}, },
vote(proposalId) { batchVote(proposalIds) {
console.debug('[kredits] vote for', proposalId); proposalIds = proposalIds.map((id) => parseInt(id))
console.debug('[kredits] vote for', proposalIds);
return this.kredits.Operator.functions.vote(proposalId) return this.kredits.Operator.functions.batchVote(proposalIds)
.then((data) => { .then((data) => {
console.debug('[kredits] vote response', data); console.debug('[kredits] vote response', data);
return data; return data;
+15
View File
@@ -46,7 +46,22 @@ ul.proposal-list {
height: 2rem; height: 2rem;
line-height: 2rem; line-height: 2rem;
padding: 0 0.6rem; padding: 0 0.6rem;
&.selected {
background-color: rgba(224,104,251, 0.6);
}
} }
} }
button {
margin-top: 1.1rem;
height: 2rem;
line-height: 2rem;
padding: 0 0.6rem;
&:disabled {
cursor: not-allowed;
color: rgba(204,204,204, 0.6);
}
}
} }
+3 -5
View File
@@ -2,7 +2,7 @@
<section id="user-account"> <section id="user-account">
{{#if kredits.currentUser }} {{#if kredits.currentUser }}
{{ kredits.currentUser.name }} {{ kredits.currentUser.name }}
{{#if kredits.currentUserIsCore }} {{#if kredits.currentUser.isCore }}
(core) (core)
{{/if}} {{/if}}
Account: {{kredits.currentUser.account}} Account: {{kredits.currentUser.account}}
@@ -32,8 +32,7 @@
<div class="content"> <div class="content">
{{!-- TODO: We need a better naming for kredits.hasAccounts --}} {{!-- TODO: We need a better naming for kredits.hasAccounts --}}
{{proposal-list proposals=proposalsOpenSorted {{proposal-list proposals=proposalsOpenSorted
confirmProposal=(action "confirmProposal") confirmProposals=(action 'confirmProposals')}}
contractInteractionEnabled=kredits.hasAccounts}}
{{!-- TODO: We need a better naming --}} {{!-- TODO: We need a better naming --}}
{{#if kredits.hasAccounts}} {{#if kredits.hasAccounts}}
1
@@ -50,8 +49,7 @@
<h2>Closed Proposals</h2> <h2>Closed Proposals</h2>
</header> </header>
<div class="content"> <div class="content">
{{proposal-list proposals=proposalsClosedSorted {{proposal-list proposals=proposalsClosedSorted}}
confirmProposal=(action "confirmProposal")}}
{{!-- TODO: We need a better naming --}} {{!-- TODO: We need a better naming --}}
{{#if kredits.hasAccounts}} {{#if kredits.hasAccounts}}