Switch to contribution token instead of proposals
No vetos yet, and only for collections (not creation). closes #20
This commit is contained in:
@@ -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.');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{{#each contributions as |contribution|}}
|
||||||
|
<li data-contribution-id={{contribution.id}} class={{if contribution.confirmed "confirmed" "unconfirmed"}}>
|
||||||
|
<p class="meta">
|
||||||
|
<span class="category {{contribution.kind}}">♥ ({{contribution.kind}})</span>
|
||||||
|
<span class="recipient">{{contribution.contributor.name}}:</span>
|
||||||
|
</p>
|
||||||
|
<p class="kredits-amount">
|
||||||
|
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
|
||||||
|
</p>
|
||||||
|
<p class="description">
|
||||||
|
<span class="description">{{contribution.description}}</span>
|
||||||
|
</p>
|
||||||
|
<p class="voting">
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import Controller from '@ember/controller';
|
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 { inject as service } from '@ember/service';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
kredits: service(),
|
kredits: service(),
|
||||||
|
currentBlock: alias('kredits.currentBlock'),
|
||||||
|
|
||||||
contributors: alias('kredits.contributors'),
|
contributors: alias('kredits.contributors'),
|
||||||
contributorsWithKredits: filter('contributors', function(contributor) {
|
contributorsWithKredits: filter('contributors', function(contributor) {
|
||||||
@@ -12,14 +14,31 @@ export default Controller.extend({
|
|||||||
contributorsSorting: Object.freeze(['balance:desc']),
|
contributorsSorting: Object.freeze(['balance:desc']),
|
||||||
contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'),
|
contributorsSorted: sort('contributorsWithKredits', 'contributorsSorting'),
|
||||||
|
|
||||||
proposals: alias('kredits.proposals'),
|
contributions: alias('kredits.contributions'),
|
||||||
proposalsOpen: filterBy('proposals', 'isExecuted', false),
|
contributionsSorting: Object.freeze(['id:desc']),
|
||||||
proposalsClosed: filterBy('proposals', 'isExecuted', true),
|
|
||||||
proposalsSorting: Object.freeze(['id:desc']),
|
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||||
proposalsClosedSorted: sort('proposalsClosed', 'proposalsSorting'),
|
return this.contributions.filter(contribution => {
|
||||||
proposalsOpenSorted: sort('proposalsOpen', 'proposalsSorting'),
|
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: {
|
actions: {
|
||||||
|
|
||||||
|
vetoContribution (/* contributionId */) {
|
||||||
|
// this.kredits.vote(proposalId).then(transaction => {
|
||||||
|
// window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash);
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
confirmProposal (proposalId) {
|
confirmProposal (proposalId) {
|
||||||
this.kredits.vote(proposalId).then(transaction => {
|
this.kredits.vote(proposalId).then(transaction => {
|
||||||
window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash);
|
window.confirm('Vote submitted to Ethereum blockhain: '+transaction.hash);
|
||||||
@@ -33,5 +52,6 @@ export default Controller.extend({
|
|||||||
return contributor;
|
return contributor;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -23,7 +23,7 @@ export default Route.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
afterModel() {
|
afterModel() {
|
||||||
return this.kredits.loadContributorsAndProposals()
|
return this.kredits.loadInitialData()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.kredits.addContractEventHandlers();
|
this.kredits.addContractEventHandlers();
|
||||||
});
|
});
|
||||||
|
|||||||
+25
-5
@@ -10,13 +10,16 @@ import { isEmpty } from '@ember/utils';
|
|||||||
import config from 'kredits-web/config/environment';
|
import config from 'kredits-web/config/environment';
|
||||||
import Contributor from 'kredits-web/models/contributor'
|
import Contributor from 'kredits-web/models/contributor'
|
||||||
import Proposal from 'kredits-web/models/proposal'
|
import Proposal from 'kredits-web/models/proposal'
|
||||||
|
import Contribution from 'kredits-web/models/contribution'
|
||||||
|
|
||||||
export default Service.extend({
|
export default Service.extend({
|
||||||
|
|
||||||
|
currentBlock: null,
|
||||||
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,
|
||||||
contributors: null,
|
contributors: null,
|
||||||
proposals: null,
|
proposals: null,
|
||||||
|
contributions: null,
|
||||||
currentUserIsContributor: notEmpty('currentUser'),
|
currentUserIsContributor: notEmpty('currentUser'),
|
||||||
currentUserIsCore: alias('currentUser.isCore'),
|
currentUserIsCore: alias('currentUser.isCore'),
|
||||||
hasAccounts: notEmpty('currentUserAccounts'),
|
hasAccounts: notEmpty('currentUserAccounts'),
|
||||||
@@ -28,6 +31,7 @@ export default Service.extend({
|
|||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this.set('contributors', []);
|
this.set('contributors', []);
|
||||||
this.set('proposals', []);
|
this.set('proposals', []);
|
||||||
|
this.set('contributions', []);
|
||||||
},
|
},
|
||||||
|
|
||||||
// this is called in the routes beforeModel(). So it is initialized before everything else
|
// this is called in the routes beforeModel(). So it is initialized before everything else
|
||||||
@@ -83,7 +87,6 @@ export default Service.extend({
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
return this.getEthProvider().then((providerAndSigner) => {
|
return this.getEthProvider().then((providerAndSigner) => {
|
||||||
|
|
||||||
let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
let kredits = new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
||||||
addresses: { Kernel: config.kreditsKernelAddress },
|
addresses: { Kernel: config.kreditsKernelAddress },
|
||||||
apm: config.kreditsApmDomain,
|
apm: config.kreditsApmDomain,
|
||||||
@@ -91,13 +94,16 @@ export default Service.extend({
|
|||||||
});
|
});
|
||||||
return kredits
|
return kredits
|
||||||
.init()
|
.init()
|
||||||
.then((kredits) => {
|
.then(async (kredits) => {
|
||||||
this.set('kredits', kredits);
|
this.set('kredits', kredits);
|
||||||
|
this.set('currentBlock', await kredits.provider.getBlockNumber());
|
||||||
|
|
||||||
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', contributorData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return kredits;
|
return kredits;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -107,11 +113,11 @@ export default Service.extend({
|
|||||||
return this.kredits.Token.functions.totalSupply();
|
return this.kredits.Token.functions.totalSupply();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
loadContributorsAndProposals() {
|
loadInitialData() {
|
||||||
return this.getContributors()
|
return this.getContributors()
|
||||||
.then(contributors => this.contributors.pushObjects(contributors))
|
.then(contributors => this.contributors.pushObjects(contributors))
|
||||||
.then(() => this.getProposals())
|
.then(() => this.getContributions())
|
||||||
.then(proposals => this.proposals.pushObjects(proposals))
|
.then(contributions => this.contributions.pushObjects(contributions))
|
||||||
},
|
},
|
||||||
|
|
||||||
addContributor(attributes) {
|
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) {
|
vote(proposalId) {
|
||||||
console.debug('[kredits] vote for', proposalId);
|
console.debug('[kredits] vote for', proposalId);
|
||||||
|
|
||||||
@@ -184,6 +200,10 @@ export default Service.extend({
|
|||||||
return this.proposals.findBy('id', proposalId.toString());
|
return this.proposals.findBy('id', proposalId.toString());
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findContributionById(contributionId) {
|
||||||
|
return this.contributions.findBy('id', contributionId.toString());
|
||||||
|
},
|
||||||
|
|
||||||
// Contract events
|
// Contract events
|
||||||
addContractEventHandlers() {
|
addContractEventHandlers() {
|
||||||
// Proposal events
|
// Proposal events
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ main section {
|
|||||||
|
|
||||||
@include media-max(small) {
|
@include media-max(small) {
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
|
|
||||||
&#proposals-open, &#proposals-closed {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
|
|||||||
@@ -119,3 +119,4 @@ button, input[type=submit] {
|
|||||||
@import "components/contributor-list";
|
@import "components/contributor-list";
|
||||||
@import "components/add-contributor";
|
@import "components/add-contributor";
|
||||||
@import "components/proposal-list";
|
@import "components/proposal-list";
|
||||||
|
@import "components/contribution-list";
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,33 +20,33 @@
|
|||||||
<h2>Contributions by type</h2>
|
<h2>Contributions by type</h2>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{chart-contributions-by-type contributions=proposals}}
|
{{chart-contributions-by-type contributions=contributions}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contributions">
|
<div id="contributions">
|
||||||
{{#if proposalsOpen}}
|
{{#if contributionsUnconfirmed}}
|
||||||
<section id="proposals-open">
|
<section id="contributions-unconfirmed">
|
||||||
<header>
|
<header>
|
||||||
<h2>Unconfirmed Contributions</h2>
|
<h2>Unconfirmed Contributions</h2>
|
||||||
</header>
|
</header>
|
||||||
<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
|
{{contribution-list contributions=contributionsUnconfirmedSorted
|
||||||
confirmProposal=(action "confirmProposal")
|
vetoContribution=(action "vetoContribution")
|
||||||
contractInteractionEnabled=kredits.hasAccounts}}
|
contractInteractionEnabled=kredits.hasAccounts}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<section id="proposals-closed">
|
<section id="contributions-confirmed">
|
||||||
<header>
|
<header>
|
||||||
<h2>Confirmed Contributions</h2>
|
<h2>Confirmed Contributions</h2>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{proposal-list proposals=proposalsClosedSorted
|
{{contribution-list contributions=contributionsConfirmedSorted
|
||||||
confirmProposal=(action "confirmProposal")}}
|
vetoContribution=(action "vetoContribution")}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{!-- TODO: We need a better naming --}}
|
{{!-- TODO: We need a better naming --}}
|
||||||
|
|||||||
@@ -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(), '');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user