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}}
-
+
- {{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(), '');
+ });
+});