From aa28a14d0466bbf8a36af841dd1247a5195d9ba6 Mon Sep 17 00:00:00 2001 From: Sebastian Kippe Date: Tue, 9 Apr 2019 12:55:44 +0200 Subject: [PATCH] Switch to contribution token instead of proposals No vetos yet, and only for collections (not creation). closes #20 --- app/components/contribution-list/component.js | 20 +++ app/components/contribution-list/template.hbs | 16 +++ app/controllers/index.js | 38 ++++-- app/models/contribution.js | 28 ++++ app/routes/application.js | 2 +- app/services/kredits.js | 30 +++- app/styles/_layout.scss | 4 - app/styles/app.scss | 1 + app/styles/components/_contribution-list.scss | 128 ++++++++++++++++++ app/templates/index.hbs | 18 +-- .../contribution-list/component-test.js | 17 +++ 11 files changed, 274 insertions(+), 28 deletions(-) create mode 100644 app/components/contribution-list/component.js create mode 100644 app/components/contribution-list/template.hbs create mode 100644 app/models/contribution.js create mode 100644 app/styles/components/_contribution-list.scss create mode 100644 tests/integration/components/contribution-list/component-test.js diff --git a/app/components/contribution-list/component.js b/app/components/contribution-list/component.js new file mode 100644 index 0000000..c490f86 --- /dev/null +++ b/app/components/contribution-list/component.js @@ -0,0 +1,20 @@ +import Component from '@ember/component'; + +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.'); + // } + // } + // + // } + +}); diff --git a/app/components/contribution-list/template.hbs b/app/components/contribution-list/template.hbs new file mode 100644 index 0000000..60885ae --- /dev/null +++ b/app/components/contribution-list/template.hbs @@ -0,0 +1,16 @@ +{{#each contributions as |contribution|}} +
  • +

    + ♥ ({{contribution.kind}}) + {{contribution.contributor.name}}: +

    +

    + {{contribution.amount}}₭S +

    +

    + {{contribution.description}} +

    +

    +

    +
  • +{{/each}} \ No newline at end of file diff --git a/app/controllers/index.js b/app/controllers/index.js index 0bd5926..21c4b91 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -1,9 +1,11 @@ import Controller from '@ember/controller'; -import { alias, filter, filterBy, sort } from '@ember/object/computed'; +import { alias, filter, sort } from '@ember/object/computed'; import { inject as service } from '@ember/service'; +import { computed } from '@ember/object'; export default Controller.extend({ kredits: service(), + currentBlock: alias('kredits.currentBlock'), contributors: alias('kredits.contributors'), contributorsWithKredits: filter('contributors', function(contributor) { @@ -12,26 +14,44 @@ export default Controller.extend({ contributorsSorting: Object.freeze(['balance:desc']), contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'), - proposals: alias('kredits.proposals'), - proposalsOpen: filterBy('proposals', 'isExecuted', false), - proposalsClosed: filterBy('proposals', 'isExecuted', true), - proposalsSorting: Object.freeze(['id:desc']), - proposalsClosedSorted: sort('proposalsClosed', 'proposalsSorting'), - proposalsOpenSorted: sort('proposalsOpen', 'proposalsSorting'), + contributions: alias('kredits.contributions'), + contributionsSorting: Object.freeze(['id:desc']), + + contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() { + return this.contributions.filter(contribution => { + return contribution.confirmedAt < this.currentBlock; + }); + }), + contributionsConfirmed: computed('contributions.[]', 'currentBlock', function() { + return this.contributions.filter(contribution => { + return contribution.confirmedAt >= this.currentBlock; + }); + }), + + contributionsUnconfirmedSorted: sort('contributionsUnconfirmed', 'contributionsSorting'), + contributionsConfirmedSorted: sort('contributionsConfirmed', 'contributionsSorting'), actions: { - confirmProposal(proposalId) { + + vetoContribution (/* contributionId */) { + // this.kredits.vote(proposalId).then(transaction => { + // window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash); + // }); + }, + + confirmProposal (proposalId) { this.kredits.vote(proposalId).then(transaction => { window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash); }); }, - save(contributor) { + save (contributor) { return this.kredits.addContributor(contributor) .then((contributor) => { this.contributors.pushObject(contributor); return contributor; }); } + } }); diff --git a/app/models/contribution.js b/app/models/contribution.js new file mode 100644 index 0000000..f5c30e0 --- /dev/null +++ b/app/models/contribution.js @@ -0,0 +1,28 @@ +import EmberObject from '@ember/object'; +import bignumber from 'kredits-web/utils/cps/bignumber'; + +export default EmberObject.extend({ + + // Contract + id: null, + contributorId: null, + amount: null, + confirmedAt: bignumber('confirmedAtBlock', 'toNumber'), + vetoed: null, + ipfsHash: null, + + creatorAccount: null, + + // IPFS + kind: null, + description: null, + details: null, + url: null, + ipfsData: '', + + init () { + this._super(...arguments); + this.set('details', {}); + } + +}); diff --git a/app/routes/application.js b/app/routes/application.js index 114b1a7..e02d727 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -23,7 +23,7 @@ export default Route.extend({ }, afterModel() { - return this.kredits.loadContributorsAndProposals() + return this.kredits.loadInitialData() .then(() => { this.kredits.addContractEventHandlers(); }); diff --git a/app/services/kredits.js b/app/services/kredits.js index e33b877..e7b05be 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -10,13 +10,16 @@ import { isEmpty } from '@ember/utils'; import config from 'kredits-web/config/environment'; import Contributor from 'kredits-web/models/contributor' import Proposal from 'kredits-web/models/proposal' +import Contribution from 'kredits-web/models/contribution' export default Service.extend({ + currentBlock: null, currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded. currentUser: null, contributors: null, proposals: null, + contributions: null, currentUserIsContributor: notEmpty('currentUser'), currentUserIsCore: alias('currentUser.isCore'), hasAccounts: notEmpty('currentUserAccounts'), @@ -28,6 +31,7 @@ export default Service.extend({ this._super(...arguments); this.set('contributors', []); this.set('proposals', []); + this.set('contributions', []); }, // this is called in the routes beforeModel(). So it is initialized before everything else @@ -83,7 +87,6 @@ export default Service.extend({ setup() { return this.getEthProvider().then((providerAndSigner) => { - let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, { addresses: { Kernel: config.kreditsKernelAddress }, apm: config.kreditsApmDomain, @@ -91,13 +94,16 @@ export default Service.extend({ }); return kredits .init() - .then((kredits) => { + .then(async (kredits) => { this.set('kredits', kredits); + this.set('currentBlock', await kredits.provider.getBlockNumber()); + if (this.currentUserAccounts && this.currentUserAccounts.length > 0) { this.getCurrentUser.then((contributorData) => { this.set('currentUser', contributorData); }); } + return kredits; }); }); @@ -107,11 +113,11 @@ export default Service.extend({ return this.kredits.Token.functions.totalSupply(); }), - loadContributorsAndProposals() { + loadInitialData() { return this.getContributors() .then(contributors => this.contributors.pushObjects(contributors)) - .then(() => this.getProposals()) - .then(proposals => this.proposals.pushObjects(proposals)) + .then(() => this.getContributions()) + .then(contributions => this.contributions.pushObjects(contributions)) }, addContributor(attributes) { @@ -154,6 +160,16 @@ export default Service.extend({ }); }, + getContributions() { + return this.kredits.Contribution.all() + .then(contributions => { + return contributions.map(contribution => { + contribution.contributor = this.contributors.findBy('id', contribution.contributorId.toString()); + return Contribution.create(contribution); + }); + }); + }, + vote(proposalId) { console.debug('[kredits] vote for', proposalId); @@ -184,6 +200,10 @@ 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 diff --git a/app/styles/_layout.scss b/app/styles/_layout.scss index ff5d53a..2655bdc 100644 --- a/app/styles/_layout.scss +++ b/app/styles/_layout.scss @@ -36,10 +36,6 @@ main section { @include media-max(small) { margin-bottom: 5rem; - - &#proposals-open, &#proposals-closed { - margin-top: 0; - } } header { diff --git a/app/styles/app.scss b/app/styles/app.scss index 80ac7a0..b3207f8 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -119,3 +119,4 @@ button, input[type=submit] { @import "components/contributor-list"; @import "components/add-contributor"; @import "components/proposal-list"; +@import "components/contribution-list"; diff --git a/app/styles/components/_contribution-list.scss b/app/styles/components/_contribution-list.scss new file mode 100644 index 0000000..49e8339 --- /dev/null +++ b/app/styles/components/_contribution-list.scss @@ -0,0 +1,128 @@ +main section { + @include media-max(small) { + &#contributions-unconfirmed, &#contributions-confirmed { + margin-top: 0; + } + } +} + +ul.contribution-list { + clear: both; + width: 100%; + list-style: none; + + li { + display: grid; + grid-template-columns: auto 5rem; + grid-row-gap: 0.5rem; + padding: 1rem 1.2rem; + background-color: rgba(255,255,255,0.1); + font-size: 1.2rem; + border-bottom: 1px solid rgba(255,255,255,0.2); + + &:first-of-type { + border-top: 1px solid rgba(255,255,255,0.2); + } + + p { + align-self: center; + margin: 0; + font-size: inherit; + line-height: 2rem; + + &.kredits-amount, &.voting { + text-align: right; + } + + &.description { + grid-column-start: span 2; + } + + &.voting { + grid-column-start: span 2; + } + } + + span { + font-size: inherit; + } + + .description { + line-height: 1.4em; + font-size: 1rem; + } + + .category { + color: $blue; + padding-right: 0.2rem; + &.community { color: $red; } + &.dev { color: $pink; } + &.design { color: $yellow; } + &.docs { color: $green; } + &.ops { color: $purple; } + } + + .amount { + font-weight: 500; + } + + .symbol { + font-size: 1rem; + font-weight: 500; + padding-left: 0.2rem; + } + + .recipient { + font-weight: 500; + } + + .votes { + font-size: 1rem; + color: $primary-color; + margin-right: 0.5rem; + } + + button { + height: 2rem; + line-height: 2rem; + padding: 0 0.6rem; + } + } + +} + +@media (min-width: 550px) { + ul.proposal-list { + li { + grid-template-columns: auto 10rem; + grid-row-gap: 0.5rem; + + p { + &.kredits-amount, &.voting { + text-align: right; + } + } + + &.unconfirmed { + p { + &.kredits-amount, &.voting { + text-align: right; + } + + &.description { + grid-column-start: span 1; + } + + &.voting { + grid-column-start: span 1; + } + } + } + + + .description { + font-size: inherit; + } + } + } +} diff --git a/app/templates/index.hbs b/app/templates/index.hbs index 3278074..947b32f 100644 --- a/app/templates/index.hbs +++ b/app/templates/index.hbs @@ -20,33 +20,33 @@

    Contributions by type

    - {{chart-contributions-by-type contributions=proposals}} + {{chart-contributions-by-type contributions=contributions}}
    - {{#if proposalsOpen}} -
    + {{#if contributionsUnconfirmed}} +

    Unconfirmed Contributions

    {{!-- TODO: We need a better naming for kredits.hasAccounts --}} - {{proposal-list proposals=proposalsOpenSorted - confirmProposal=(action "confirmProposal") - contractInteractionEnabled=kredits.hasAccounts}} + {{contribution-list contributions=contributionsUnconfirmedSorted + vetoContribution=(action "vetoContribution") + contractInteractionEnabled=kredits.hasAccounts}}
    {{/if}} -
    +

    Confirmed Contributions

    - {{proposal-list proposals=proposalsClosedSorted - confirmProposal=(action "confirmProposal")}} + {{contribution-list contributions=contributionsConfirmedSorted + vetoContribution=(action "vetoContribution")}}
    {{!-- TODO: We need a better naming --}} diff --git a/tests/integration/components/contribution-list/component-test.js b/tests/integration/components/contribution-list/component-test.js new file mode 100644 index 0000000..be94ef3 --- /dev/null +++ b/tests/integration/components/contribution-list/component-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import hbs from 'htmlbars-inline-precompile'; + +module('Integration | Component | contribution-list', function(hooks) { + setupRenderingTest(hooks); + + test('it renders', async function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs`{{contribution-list}}`); + + assert.equal(this.element.textContent.trim(), ''); + }); +});