diff --git a/.template-lintrc.js b/.template-lintrc.js index b45e96f..02bee12 100644 --- a/.template-lintrc.js +++ b/.template-lintrc.js @@ -1,5 +1,9 @@ 'use strict'; module.exports = { - extends: 'recommended' + extends: 'recommended', + + rules: { + 'simple-unless': false + } }; diff --git a/app/components/contribution-list/component.js b/app/components/contribution-list/component.js index c490f86..f85da1d 100644 --- a/app/components/contribution-list/component.js +++ b/app/components/contribution-list/component.js @@ -5,16 +5,16 @@ export default Component.extend({ tagName: 'ul', classNames: ['contribution-list'], - // actions: { - // - // veto (contributionId) { - // if (this.contractInteractionEnabled) { - // this.vetoContribution(contributionId); - // } else { - // window.alert('Only members can veto contributions. Please ask someone to set you up.'); - // } - // } - // - // } + actions: { + + veto (contributionId) { + if (this.contractInteractionEnabled) { + this.vetoContribution(contributionId); + } else { + window.alert('Only members can veto contributions. Please ask someone to set you up.'); + } + } + + } }); diff --git a/app/components/contribution-list/template.hbs b/app/components/contribution-list/template.hbs index d53ad52..29a5760 100644 --- a/app/components/contribution-list/template.hbs +++ b/app/components/contribution-list/template.hbs @@ -1,5 +1,5 @@ {{#each contributions as |contribution|}} -
  • +
  • {{user-avatar contributor=contribution.contributor}} ({{contribution.kind}}) @@ -14,5 +14,12 @@

    {{contribution.amount}}₭S

    + {{#unless contribution.vetoed}} + {{#unless (is-confirmed-contribution contribution)}} +

    + +

    + {{/unless}} + {{/unless}}
  • {{/each}} \ No newline at end of file diff --git a/app/controllers/index.js b/app/controllers/index.js index 7b2cc77..31009ad 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -35,15 +35,15 @@ export default Controller.extend({ actions: { - vetoContribution (/* contributionId */) { - // this.kredits.vote(proposalId).then(transaction => { - // window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash); - // }); + vetoContribution (contributionId) { + this.kredits.veto(contributionId).then(transaction => { + console.debug('[controllers:index] Veto submitted to Ethereum blockhain: '+transaction.hash); + }); }, confirmProposal (proposalId) { this.kredits.vote(proposalId).then(transaction => { - window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash); + console.debug('[controllers:index] Vote submitted to Ethereum blockhain: '+transaction.hash); }); }, diff --git a/app/helpers/contribution-status.js b/app/helpers/contribution-status.js new file mode 100644 index 0000000..79e4bbb --- /dev/null +++ b/app/helpers/contribution-status.js @@ -0,0 +1,20 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; +import { alias } from '@ember/object/computed'; + +export default Helper.extend({ + + kredits: service(), + currentBlock: alias('kredits.currentBlock'), + + compute([contribution]) { + if (contribution.vetoed) { + return 'vetoed'; + } else if (contribution.confirmedAt > this.currentBlock) { + return 'unconfirmed'; + } else { + return 'confirmed' + } + } + +}); diff --git a/app/helpers/is-confirmed-contribution.js b/app/helpers/is-confirmed-contribution.js new file mode 100644 index 0000000..0f63b76 --- /dev/null +++ b/app/helpers/is-confirmed-contribution.js @@ -0,0 +1,15 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; +import { alias } from '@ember/object/computed'; + +export default Helper.extend({ + + kredits: service(), + currentBlock: alias('kredits.currentBlock'), + + compute([contribution]) { + return !contribution.vetoed && + (contribution.confirmedAt <= this.currentBlock); + } + +}); diff --git a/app/services/kredits.js b/app/services/kredits.js index ca78947..e66aeee 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -52,9 +52,10 @@ export default Service.extend({ }); } - function instantiateWithAccount (web3Provider, context) { + async function instantiateWithAccount (web3Provider, context) { console.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask'); ethProvider = new ethers.providers.Web3Provider(web3Provider); + // const network = await ethProvider.getNetwork(); ethProvider.listAccounts().then(accounts => { context.set('currentUserAccounts', accounts); const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner(); @@ -188,12 +189,22 @@ export default Service.extend({ console.debug('[kredits] vote for', proposalId); return this.kredits.Proposal.functions.vote(proposalId) - .then((data) => { + .then(data => { console.debug('[kredits] vote response', data); return data; }); }, + veto(contributionId) { + console.debug('[kredits] veto against', contributionId); + + return this.kredits.Contribution.functions.veto(contributionId, { gasLimit: 300000 }) + .then(data => { + console.debug('[kredits] veto response', data); + return data; + }); + }, + getCurrentUser: computed('kredits.provider', function() { if (isEmpty(this.currentUserAccounts)) { return RSVP.resolve(); @@ -214,23 +225,30 @@ export default Service.extend({ return this.proposals.findBy('id', proposalId.toString()); }, - findContributionById(contributionId) { - return this.contributions.findBy('id', contributionId.toString()); - }, - // Contract events addContractEventHandlers() { - // Proposal events + this.kredits.Contribution + .on('ContributionVetoed', this.handleContributionVetoed.bind(this)) + this.kredits.Proposal .on('ProposalCreated', this.handleProposalCreated.bind(this)) .on('ProposalVoted', this.handleProposalVoted.bind(this)) .on('ProposalExecuted', this.handleProposalExecuted.bind(this)); - // Token events this.kredits.Token .on('Transfer', this.handleTransfer.bind(this)); }, + handleContributionVetoed(contributionId) { + console.debug('[kredits] ContributionVetoed event received for ', contributionId); + const contribution = this.contributions.findBy('id', contributionId); + console.debug('[kredits] contribution', contribution); + + if (contribution) { + contribution.set('vetoed', true); + } + }, + handleProposalCreated(proposalId) { let proposal = this.findProposalById(proposalId); diff --git a/app/styles/_buttons.scss b/app/styles/_buttons.scss new file mode 100644 index 0000000..8a6956a --- /dev/null +++ b/app/styles/_buttons.scss @@ -0,0 +1,30 @@ +button, input[type=submit] { + display: inline-block; + padding: 0.6rem 2rem; + background-color: rgba(22, 21, 40, 0.6); + border: 1px solid rgba(22, 21, 40, 1); + border-radius: 3px; + color: $primary-color; + font-weight: 500; + text-transform: uppercase; + cursor: pointer; + letter-spacing: 0.1em; + + &:hover { + background-color: rgba(22, 21, 40, 0.8); + } + + &.small { + font-size: 0.8rem; + padding: 0.2rem 0.8rem; + } + + &.danger { + color: $red; + background-color: rgba(40, 21, 21, 0.6); + + &:hover { + background-color: rgba(40, 21, 21, 0.8); + } + } +} diff --git a/app/styles/app.scss b/app/styles/app.scss index 84ec881..69555ef 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -90,22 +90,6 @@ section { } } -button, input[type=submit] { - display: inline-block; - border: 1px solid rgba(22, 21, 40, 1); - background-color: rgba(22, 21, 40, 0.6); - color: $primary-color; - border-radius: 3px; - font-weight: 500; - text-transform: uppercase; - cursor: pointer; - letter-spacing: 0.1em; - - &:hover { - background-color: rgba(22, 21, 40, 0.8); - } -} - @media (min-width: 550px) { section { h2 { @@ -114,6 +98,7 @@ button, input[type=submit] { } } +@import "buttons"; @import "components/topbar"; @import "components/loading-spinner"; @import "components/contributor-list"; diff --git a/app/styles/components/_contribution-list.scss b/app/styles/components/_contribution-list.scss index c865532..71367b9 100644 --- a/app/styles/components/_contribution-list.scss +++ b/app/styles/components/_contribution-list.scss @@ -13,7 +13,7 @@ ul.contribution-list { li { display: grid; - grid-template-columns: auto 5rem; + grid-template-columns: auto 5rem 5rem; grid-row-gap: 0.5rem; padding: 1rem 1.2rem; background-color: rgba(255,255,255,0.1); @@ -24,6 +24,16 @@ ul.contribution-list { border-top: 1px solid rgba(255,255,255,0.2); } + &.confirmed { + grid-template-columns: auto 5rem; + } + + &.vetoed { + grid-template-columns: auto 5rem; + text-decoration: line-through; + opacity: 0.6; + } + p { align-self: center; margin: 0; @@ -33,14 +43,6 @@ ul.contribution-list { &.kredits-amount, &.voting { text-align: right; } - - &.description { - grid-column-start: span 2; - } - - &.voting { - grid-column-start: span 2; - } } span { @@ -92,12 +94,6 @@ ul.contribution-list { color: $primary-color; margin-right: 0.5rem; } - - button { - height: 2rem; - line-height: 2rem; - padding: 0 0.6rem; - } } } diff --git a/app/styles/components/_proposal-list.scss b/app/styles/components/_proposal-list.scss index 2599a0b..2527ad2 100644 --- a/app/styles/components/_proposal-list.scss +++ b/app/styles/components/_proposal-list.scss @@ -73,12 +73,6 @@ ul.proposal-list { color: $primary-color; margin-right: 0.5rem; } - - button { - height: 2rem; - line-height: 2rem; - padding: 0 0.6rem; - } } } diff --git a/tests/unit/helpers/contribution-status.js b/tests/unit/helpers/contribution-status.js new file mode 100644 index 0000000..785b56c --- /dev/null +++ b/tests/unit/helpers/contribution-status.js @@ -0,0 +1,21 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Helper | contribution-status', function (hooks) { + setupTest(hooks); + + test('returns the appropriate status', function (assert) { + const contributionStatus = this.owner.factoryFor('helper:contribution-status').create(); + const kredits = this.owner.lookup('service:kredits'); + + kredits.set('currentBlock', 23000); + + const contributionUnconfirmed = { confirmedAt: 23001, vetoed: false }; + const contributionConfirmed = { confirmedAt: 21000, vetoed: false }; + const contributionVetoed = { confirmedAt: 23001, vetoed: true }; + + assert.eq(contributionStatus.compute([contributionUnconfirmed]), 'unconfirmed'); + assert.eq(contributionStatus.compute([contributionConfirmed]), 'confirmed'); + assert.eq(contributionStatus.compute([contributionVetoed]), 'vetoed'); + }); +}); diff --git a/tests/unit/helpers/is-confirmed-contribution.js b/tests/unit/helpers/is-confirmed-contribution.js new file mode 100644 index 0000000..3db7534 --- /dev/null +++ b/tests/unit/helpers/is-confirmed-contribution.js @@ -0,0 +1,21 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Helper | contribution-status', function (hooks) { + setupTest(hooks); + + test('returns the appropriate status', function (assert) { + const contributionStatus = this.owner.factoryFor('helper:contribution-status').create(); + const kredits = this.owner.lookup('service:kredits'); + + kredits.set('currentBlock', 23000); + + const contributionUnconfirmed = { confirmedAt: 23001, vetoed: false }; + const contributionConfirmed = { confirmedAt: 21000, vetoed: false }; + const contributionVetoed = { confirmedAt: 23001, vetoed: true }; + + assert.notOk(contributionStatus.compute([contributionUnconfirmed]), 'unconfirmed'); + assert.notOk(contributionStatus.compute([contributionVetoed]), 'vetoed'); + assert.ok(contributionStatus.compute([contributionConfirmed]), 'confirmed'); + }); +});