Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 970b390509 | |||
| b8f4040def | |||
| 8fbecd928b | |||
| b1db64a07d | |||
| 22affa7663 | |||
| b125d941ba | |||
| 9b37f130f3 | |||
| 993cd85e4a | |||
| e51586a78c | |||
| 8aa86fa553 | |||
| 510f7de693 | |||
| 918ae5b2d1 | |||
| 6263254a1b | |||
| 99e921c302 | |||
| 7f2f781a77 | |||
| 3571cc633e | |||
| 2011636173 | |||
| cbb15f4d6f | |||
| 33072900e9 | |||
| c98a50cd42 | |||
| 1f41dbcae5 | |||
| a918719b8f | |||
| 7b91e64af4 | |||
| 0afbeea01b | |||
| 0510cf2389 | |||
| c893f91ce2 | |||
| eced2f423f | |||
| 6eeb505701 | |||
| 4cb3f5d8ba | |||
| 372a8a9b2d | |||
| a714601c63 | |||
| 240d446b97 | |||
| 33e81a7668 | |||
| f8b75cca61 | |||
| 32e1bbd617 | |||
| 1de26004ab | |||
| 0a66c19b0a | |||
| 40d6b9905a | |||
| 228825053c | |||
| eda0f7a467 | |||
| 3052c9dfc5 | |||
| dbb4094eab | |||
| f875cd7b91 | |||
| 45f17100a2 | |||
| dda5a8d350 | |||
| 4f57ceaee9 | |||
| 2405e3e118 | |||
| d7aaadea2b | |||
| 8d07c7d741 | |||
| 2b260bccd5 | |||
| 7299394809 | |||
| 0b7afc0dd5 | |||
| f57e90941c | |||
| be6c5574ff | |||
| 859cca3820 | |||
| ba1cd1cd49 | |||
| a1f73b0307 | |||
| 3be14b945a | |||
| 675dff7314 | |||
| 2ff76c902b | |||
| 642015700e | |||
| 969ef8d1ed | |||
| b5f8216f7f | |||
| e6740d9b4f | |||
| 2e61b4c8ad | |||
| 62a0733a11 | |||
| 09aa8a5669 | |||
| 01c1a9c804 | |||
| 0a2f44cec8 | |||
| e7fa188797 | |||
| b8ded8e6a8 | |||
| 26cebc11c6 | |||
| 2a9f9a50c4 | |||
| 7c98de3e6a | |||
| cd6fbfe17c | |||
| ad76fb2369 | |||
| 0f9664410b | |||
| d05356cfe4 | |||
| 1de2e002bb | |||
| 7a072f2586 | |||
| 3eb58451cc | |||
| 9cc4f85a69 | |||
| 29602d5c36 | |||
| b15191aa0e | |||
| d6d4e51ea3 | |||
| bb3696addc | |||
| 96ea4bb993 | |||
| 433d6b0d59 | |||
| 0235313282 | |||
| 10763b1e7f | |||
| 1f39884eef | |||
| d0e10babfe |
@@ -45,6 +45,30 @@ Make use of the many generators for code, try `ember help generate` for more det
|
|||||||
|
|
||||||
Specify what it takes to deploy your app.
|
Specify what it takes to deploy your app.
|
||||||
|
|
||||||
|
|
||||||
|
## Working with locally deployed contracts
|
||||||
|
|
||||||
|
For development you might want to run your own development chain with your own development contracts.
|
||||||
|
The kredits-contracts helper scripts are available in kredits-web. See [kredits-contracts](https://github.com/67P/kredits-contracts) for more information.
|
||||||
|
|
||||||
|
Start your parity node with a KreditsChain:
|
||||||
|
|
||||||
|
npm run kredits-parity
|
||||||
|
|
||||||
|
Deploy the contracts to your local chain:
|
||||||
|
|
||||||
|
npm run kredits-deploy
|
||||||
|
|
||||||
|
Run kredits-web with your localy deployed contracts:
|
||||||
|
|
||||||
|
KREDITS_CONTRACT_ADDR=`kredits-inspect -c Kredits -r -w address` \
|
||||||
|
TOKEN_CONTRACT_ADDR=`kredits-inspect -c Token -r -w address` \
|
||||||
|
npm start
|
||||||
|
|
||||||
|
You need to have `./node_modules/.bin` in your path for this to work. Otherwise simply copy the contract addresses and set the environment variables manually.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Further Reading / Useful Links
|
## Further Reading / Useful Links
|
||||||
|
|
||||||
* [ember.js](http://emberjs.com/)
|
* [ember.js](http://emberjs.com/)
|
||||||
|
|||||||
@@ -1,33 +1,79 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import Contributor from 'kredits-web/models/contributor';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
const {
|
||||||
|
Component,
|
||||||
|
isPresent,
|
||||||
|
inject: {
|
||||||
|
service
|
||||||
|
},
|
||||||
|
computed
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
id: null,
|
export default Component.extend({
|
||||||
realName: null,
|
|
||||||
address: null,
|
|
||||||
|
|
||||||
classId: function() {
|
kredits: service(),
|
||||||
let value = this.get('id');
|
|
||||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
|
||||||
}.property('id'),
|
|
||||||
|
|
||||||
classRealName: function() {
|
newContributor: null,
|
||||||
let value = this.get('realName');
|
inProgress: false,
|
||||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
|
||||||
}.property('realName'),
|
|
||||||
|
|
||||||
classAddress: function() {
|
isValidAddress: function() {
|
||||||
let value = this.get('address');
|
return this.get('kredits.web3')
|
||||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
.isAddress(this.get('newContributor.address'));
|
||||||
}.property('address'),
|
}.property('kredits.web3', 'newContributor.address'),
|
||||||
|
|
||||||
|
isValidName: function() {
|
||||||
|
return isPresent(this.get('newContributor.name'));
|
||||||
|
}.property('newContributor.name'),
|
||||||
|
|
||||||
|
isValidURL: function() {
|
||||||
|
return isPresent(this.get('newContributor.url'));
|
||||||
|
}.property('newContributor.url'),
|
||||||
|
|
||||||
|
isValidGithubUID: function() {
|
||||||
|
return isPresent(this.get('newContributor.github_uid'));
|
||||||
|
}.property('newContributor.github_uid'),
|
||||||
|
|
||||||
|
isValidGithubUsername: function() {
|
||||||
|
return isPresent(this.get('newContributor.github_username'));
|
||||||
|
}.property('newContributor.github_username'),
|
||||||
|
|
||||||
|
isValidWikiUsername: function() {
|
||||||
|
return isPresent(this.get('newContributor.wiki_username'));
|
||||||
|
}.property('newContributor.wiki_username'),
|
||||||
|
|
||||||
|
isValid: computed.and(
|
||||||
|
'isValidAddress',
|
||||||
|
'isValidName',
|
||||||
|
'isValidGithubUID'
|
||||||
|
),
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
this.setProperties({
|
||||||
|
newContributor: Contributor.create({ kind: 'person' }),
|
||||||
|
inProgress: false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
console.log('id', this.get('id'));
|
if (!this.get('contractInteractionEnabled')) {
|
||||||
console.log('realName', this.get('realName'));
|
alert('Only core team members can add new contributors. Please ask someone to set you up.');
|
||||||
console.log('address', this.get('address'));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.get('isValid')) {
|
||||||
|
this.set('inProgress', true);
|
||||||
|
|
||||||
|
this.get('kredits').addContributor(this.get('newContributor')).then(contributor => {
|
||||||
|
this.reset();
|
||||||
|
this.get('contributors').pushObject(contributor);
|
||||||
|
window.scroll(0,0);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Invalid data. Please review and try again.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,59 @@
|
|||||||
<form {{action "save" on="submit"}}>
|
<form {{action "save" on="submit"}}>
|
||||||
<p>
|
<p>
|
||||||
{{input name="id"
|
{{input type="checkbox" name="is-core" id="is-core" checked=newContributor.isCore}}
|
||||||
type="text"
|
<label for="is-core" class="checkbox">
|
||||||
placeholder="GitHub UID (123)"
|
Core team member (can add contributors)
|
||||||
value=id
|
</label>
|
||||||
class=classId}}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{input name="realname"
|
|
||||||
type="text"
|
|
||||||
placeholder="Carl Sagan"
|
|
||||||
value=realName
|
|
||||||
class=classRealName}}
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{{input name="address"
|
{{input name="address"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||||
value=address
|
value=newContributor.address
|
||||||
class=classAddress}}
|
class=(if isValidAddress 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<select required onchange={{action (mut newContributor.kind) value="target.value"}}>
|
||||||
|
<option value="person" selected={{eq newContributor.kind "person"}}>Person</option>
|
||||||
|
<option value="organization" selected={{eq newContributor.kind "organization"}}>Organization</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input name="name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
value=newContributor.name
|
||||||
|
class=(if isValidName 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input name="url"
|
||||||
|
type="text"
|
||||||
|
placeholder="URL"
|
||||||
|
value=newContributor.url
|
||||||
|
class=(if isValidURL 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input name="github_uid"
|
||||||
|
type="text"
|
||||||
|
placeholder="GitHub UID (123)"
|
||||||
|
value=newContributor.github_uid
|
||||||
|
class=(if isValidGithubUID 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input name="github_username"
|
||||||
|
type="text"
|
||||||
|
placeholder="GitHub username"
|
||||||
|
value=newContributor.github_username
|
||||||
|
class=(if isValidGithubUsername 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input name="wiki_username"
|
||||||
|
type="text"
|
||||||
|
placeholder="Wiki Username"
|
||||||
|
value=newContributor.wiki_username
|
||||||
|
class=(if isValidWikiUsername 'valid' '')}}
|
||||||
</p>
|
</p>
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
<input type="submit" value="Save">
|
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const {
|
||||||
|
Component,
|
||||||
|
isPresent,
|
||||||
|
inject: {
|
||||||
|
service
|
||||||
|
},
|
||||||
|
computed
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
|
||||||
|
kredits: service(),
|
||||||
|
|
||||||
|
proposal: null,
|
||||||
|
contributors: null,
|
||||||
|
inProgress: false,
|
||||||
|
|
||||||
|
isValidRecipient: computed('proposal.recipientAddress', function() {
|
||||||
|
return this.get('kredits.web3').isAddress(this.get('proposal.recipientAddress'));
|
||||||
|
}),
|
||||||
|
|
||||||
|
isValidAmount: computed('proposal.amount', function() {
|
||||||
|
return parseInt(this.get('proposal.amount'), 10) > 0;
|
||||||
|
}),
|
||||||
|
|
||||||
|
isValidUrl: computed('proposal.url', function() {
|
||||||
|
return isPresent(this.get('proposal.url'));
|
||||||
|
}),
|
||||||
|
|
||||||
|
isValidDescription: computed('proposal.description', function() {
|
||||||
|
return isPresent(this.get('proposal.description'));
|
||||||
|
}),
|
||||||
|
|
||||||
|
isValid: computed.and('isValidRecipient',
|
||||||
|
'isValidAmount',
|
||||||
|
'isValidDescription'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
save() {
|
||||||
|
if (! this.get('isValid')) {
|
||||||
|
alert('Invalid data. Please review and try again.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.set('inProgress', true);
|
||||||
|
let proposal = this.get('proposal');
|
||||||
|
|
||||||
|
// Set the recipient's IPFS profile hash so it can be used in the
|
||||||
|
// contribution object (which is to be stored in IPFS as well)
|
||||||
|
let contributor = this.get('contributors').findBy('address', proposal.get('recipientAddress'));
|
||||||
|
proposal.set('recipientProfile', contributor.get('ipfsHash'));
|
||||||
|
|
||||||
|
this.get('kredits').addProposal(proposal)
|
||||||
|
.then(() => {
|
||||||
|
this.attrs.onSave();
|
||||||
|
}).catch((error) => {
|
||||||
|
Ember.Logger.error('[add-proposal] error creating the proposal', error);
|
||||||
|
alert('Something went wrong.');
|
||||||
|
}).finally(() => {
|
||||||
|
this.set('inProgress', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<form {{action "save" on="submit"}}>
|
||||||
|
<p>
|
||||||
|
<select required onchange={{action (mut proposal.recipientAddress) value="target.value"}}>
|
||||||
|
<option value="" selected disabled hidden>Contributor</option>
|
||||||
|
{{#each contributors as |contributor|}}
|
||||||
|
<option value={{contributor.address}} selected={{eq proposal.recipientAddress contributor.address}}>{{contributor.github_username}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<select required onchange={{action (mut proposal.kind) value="target.value"}}>
|
||||||
|
<option value="community" selected={{eq proposal.kind "community"}}>Community</option>
|
||||||
|
<option value="design" selected={{eq proposal.kind "design"}}>Design</option>
|
||||||
|
<option value="dev" selected={{eq proposal.kind "dev"}}>Development</option>
|
||||||
|
<option value="docs" selected={{eq proposal.kind "docs"}}>Documentation</option>
|
||||||
|
<option value="ops" selected={{eq proposal.kind "ops"}}>IT Operations</option>
|
||||||
|
</select>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input type="text"
|
||||||
|
placeholder="100"
|
||||||
|
value=proposal.amount
|
||||||
|
class=(if isValidAmount 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input type="text"
|
||||||
|
placeholder="Description"
|
||||||
|
value=proposal.description
|
||||||
|
class=(if isValidDescription 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{input type="text"
|
||||||
|
placeholder="URL (optional)"
|
||||||
|
value=proposal.url
|
||||||
|
class=(if isValidUrl 'valid' '')}}
|
||||||
|
</p>
|
||||||
|
<p class="actions">
|
||||||
|
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
||||||
|
{{#link-to 'index'}}Back{{/link-to}}
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Component.extend({
|
||||||
|
|
||||||
|
tagName: 'ul',
|
||||||
|
classNames: ['contribution-list'],
|
||||||
|
|
||||||
|
contributionsWithContributors: function() {
|
||||||
|
return this.get('contributions').map((c) => {
|
||||||
|
c.set('contributor', this.get('contributors')
|
||||||
|
.findBy('address', c.get('recipientAddress')));
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
}.property('contributions.@each')
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{{#each contributionsWithContributors as |contribution|}}
|
||||||
|
<li title="({{contribution.kind}}) {{contribution.description}}">
|
||||||
|
<span class="category {{contribution.kind}}">♥</span>
|
||||||
|
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
|
||||||
|
for <span class="recipient">{{contribution.contributor.name}}</span>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
@@ -3,6 +3,20 @@ import Ember from 'ember';
|
|||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
|
|
||||||
tagName: 'table',
|
tagName: 'table',
|
||||||
classNames: 'contributor-list'
|
classNames: 'contributor-list',
|
||||||
|
selectedContributor: null,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
|
||||||
|
toggleContributorInfo(contributor) {
|
||||||
|
if (contributor.get('showMetadata')) {
|
||||||
|
contributor.set('showMetadata', false);
|
||||||
|
} else {
|
||||||
|
this.get('contributors').setEach('showMetadata', false);
|
||||||
|
contributor.set('showMetadata', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{{#each contributors as |contributor|}}
|
{{#each contributors as |contributor|}}
|
||||||
<tr>
|
<tr class="{{if contributor.isCurrentUser 'current-user'}}" {{action "toggleContributorInfo" contributor}}>
|
||||||
<td class="person">
|
<td class="person">
|
||||||
<img class="avatar" src={{contributor.avatarURL}}>
|
<img class="avatar" src={{contributor.avatarURL}}>
|
||||||
{{contributor.github_username}}
|
{{contributor.name}}
|
||||||
</td>
|
</td>
|
||||||
<td class="kredits">
|
<td class="kredits">
|
||||||
<span class="amount">{{contributor.kredits}}</span>
|
<span class="amount">{{contributor.kredits}}</span>
|
||||||
<span class="symbol">₭S</span>
|
<span class="symbol">₭S</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="metadata {{if contributor.isCurrentUser 'current-user'}} {{if contributor.showMetadata 'visible'}}">
|
||||||
|
<td colspan="2">
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://testnet.etherscan.io/address/{{contributor.address}}">Inspect Ethereum transactions</a></li>
|
||||||
|
{{#if contributor.ipfsHash}}
|
||||||
|
<li><a href="https://ipfs.io/ipfs/{{contributor.ipfsHash}}">Inspect IPFS profile</a></li>
|
||||||
|
{{/if}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ export default Ember.Component.extend({
|
|||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
confirm(proposalId) {
|
confirm(proposalId) {
|
||||||
|
if (this.get('contractInteractionEnabled')) {
|
||||||
this.sendAction('confirmAction', proposalId);
|
this.sendAction('confirmAction', proposalId);
|
||||||
|
} else {
|
||||||
|
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{{#each proposals as |proposal|}}
|
{{#each proposals as |proposal|}}
|
||||||
<li>
|
<li data-proposal-id={{proposal.id}} title="({{proposal.kind}}) {{proposal.description}}">
|
||||||
<span class="id">#{{proposal.id}}</span>
|
<span class="category {{proposal.kind}}">♥</span>
|
||||||
Issue{{#if proposal.executed}}d{{/if}}
|
|
||||||
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
|
||||||
to <span class="recipient" title="{{proposal.recipientAddress}}">{{proposal.recipientName}}</span>
|
for <span class="recipient">{{proposal.recipientName}}</span>
|
||||||
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
|
||||||
|
|
||||||
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import Proposal from 'kredits-web/models/proposal';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
computed,
|
computed,
|
||||||
@@ -22,28 +23,117 @@ export default Ember.Controller.extend({
|
|||||||
let proposals = this.get('model.proposals')
|
let proposals = this.get('model.proposals')
|
||||||
.filterBy('executed', false)
|
.filterBy('executed', false)
|
||||||
.map(p => {
|
.map(p => {
|
||||||
p.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
return proposals;
|
return proposals;
|
||||||
}.property('model.proposals.[]', 'model.contributors.[]'),
|
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||||
|
|
||||||
proposalsClosed: function() {
|
proposalsClosed: function() {
|
||||||
let proposals = this.get('model.proposals')
|
let proposals = this.get('model.proposals')
|
||||||
.filterBy('executed', true)
|
.filterBy('executed', true)
|
||||||
.map(p => {
|
.map(p => {
|
||||||
p.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
return proposals;
|
return proposals;
|
||||||
}.property('model.proposals.[]', 'model.contributors.[]'),
|
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||||
|
|
||||||
proposalsSorting: ['id:desc'],
|
proposalsSorting: ['id:desc'],
|
||||||
proposalsClosedSorted: Ember.computed.sort('proposalsClosed', 'proposalsSorting'),
|
proposalsClosedSorted: Ember.computed.sort('proposalsClosed', 'proposalsSorting'),
|
||||||
proposalsOpenSorted: Ember.computed.sort('proposalsOpen', 'proposalsSorting'),
|
proposalsOpenSorted: Ember.computed.sort('proposalsOpen', 'proposalsSorting'),
|
||||||
|
|
||||||
|
contributorsWithKredits: function() {
|
||||||
|
return this.get('model.contributors').filter(c => {
|
||||||
|
return c.get('kredits') !== 0;
|
||||||
|
});
|
||||||
|
}.property('model.contributors.@each.kredits'),
|
||||||
|
|
||||||
contributorsSorting: ['kredits:desc'],
|
contributorsSorting: ['kredits:desc'],
|
||||||
contributorsSorted: Ember.computed.sort('model.contributors', 'contributorsSorting'),
|
contributorsSorted: Ember.computed.sort('contributorsWithKredits', 'contributorsSorting'),
|
||||||
|
|
||||||
|
watchContractEvents: function() {
|
||||||
|
let events = this.get('kredits.kreditsContract')
|
||||||
|
.allEvents(/* [additionalFilterObject], */);
|
||||||
|
|
||||||
|
events.watch((error, data) => {
|
||||||
|
Ember.Logger.debug('[index] Received contract event', data);
|
||||||
|
|
||||||
|
switch (data.event) {
|
||||||
|
case 'ProposalCreated':
|
||||||
|
this._handleProposalCreated(data);
|
||||||
|
break;
|
||||||
|
case 'ProposalExecuted':
|
||||||
|
this._handleProposalExecuted(data);
|
||||||
|
break;
|
||||||
|
case 'ProposalVoted':
|
||||||
|
this._handleProposalVoted(data);
|
||||||
|
break;
|
||||||
|
case 'Transfer':
|
||||||
|
this._handleTransfer(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}.on('init'),
|
||||||
|
|
||||||
|
_handleProposalCreated(data) {
|
||||||
|
if (Ember.isPresent(this.get('model.proposals')
|
||||||
|
.findBy('id', data.args.id.toNumber()))) {
|
||||||
|
Ember.Logger.debug('[index] proposal exists, not adding from event');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let proposal = Proposal.create({
|
||||||
|
id: data.args.id.toNumber(),
|
||||||
|
creatorAddress: data.args.creator,
|
||||||
|
recipientAddress: data.args.recipient,
|
||||||
|
recipientName: null,
|
||||||
|
votesCount: 0,
|
||||||
|
votesNeeded: 2,
|
||||||
|
amount: data.args.amount.toNumber(),
|
||||||
|
executed: false,
|
||||||
|
url: data.args.url,
|
||||||
|
ipfsHash: data.args.ipfsHash
|
||||||
|
});
|
||||||
|
|
||||||
|
this.get('model.proposals').pushObject(proposal);
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleProposalExecuted(data) {
|
||||||
|
if (this.get('model.proposals')
|
||||||
|
.findBy('id', data.args.id.toNumber())
|
||||||
|
.get('executed')) {
|
||||||
|
Ember.Logger.debug('[index] proposal already executed, not adding from event');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get('model.proposals')
|
||||||
|
.findBy('id', data.args.id.toNumber())
|
||||||
|
.setProperties({
|
||||||
|
'executed': true,
|
||||||
|
'votesCount': 2 // TODO use real count
|
||||||
|
});
|
||||||
|
|
||||||
|
this.get('model.contributors')
|
||||||
|
.findBy('address', data.args.recipient)
|
||||||
|
.incrementProperty('kredits', data.args.amount.toNumber());
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleProposalVoted(data) {
|
||||||
|
this.get('model.proposals')
|
||||||
|
.findBy('id', data.args.id.toNumber())
|
||||||
|
.incrementProperty('votesCount', 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleTransfer(data) {
|
||||||
|
this.get('model.contributors')
|
||||||
|
.findBy('address', data.args.from)
|
||||||
|
.incrementProperty('kredits', - data.args.value.toNumber());
|
||||||
|
this.get('model.contributors')
|
||||||
|
.findBy('address', data.args.to)
|
||||||
|
.incrementProperty('kredits', data.args.value.toNumber());
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
import QueryParams from 'ember-parachute';
|
||||||
|
|
||||||
|
export const queryParams = new QueryParams({
|
||||||
|
recipient: {
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
defaultValue: ''
|
||||||
|
},
|
||||||
|
ipfsHash: {
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Ember.Controller.extend(queryParams.Mixin, {
|
||||||
|
|
||||||
|
contributors: null,
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
onSave() {
|
||||||
|
this.transitionToRoute('index');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+7
-1
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>KreditsWeb</title>
|
<title>Kosmos Kredits</title>
|
||||||
<meta name="description" content="">
|
<meta name="description" content="">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
@@ -13,6 +13,12 @@
|
|||||||
<link rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
<link rel="stylesheet" href="{{rootURL}}assets/kredits-web.css">
|
||||||
|
|
||||||
{{content-for "head-footer"}}
|
{{content-for "head-footer"}}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
window.windowLoadComplete = true;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{content-for "body"}}
|
{{content-for "body"}}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* This pauses the app's boot process until the window load event is fired.
|
||||||
|
* It allows the user provided Web3 instance (e.g. Metamask, Mist Browser)
|
||||||
|
* to be inserted before we try to use it.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'defer-loading',
|
||||||
|
initialize: function(application) {
|
||||||
|
// Load event already fired, so web3 should be loaded if available
|
||||||
|
if (window.windowLoadComplete) { return true; }
|
||||||
|
|
||||||
|
// Pause app loading
|
||||||
|
application.deferReadiness();
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
// Continue app loading
|
||||||
|
application.advanceReadiness();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
export default Ember.Object.extend({
|
||||||
|
|
||||||
|
// blockNumber: null,
|
||||||
|
// blockHash: null,
|
||||||
|
// transactionHash: null,
|
||||||
|
recipientAddress: null,
|
||||||
|
contributorId: null,
|
||||||
|
ipfsHash: null,
|
||||||
|
amount: null,
|
||||||
|
|
||||||
|
contributor: null,
|
||||||
|
contribution: null,
|
||||||
|
kind: null,
|
||||||
|
description: null,
|
||||||
|
url: null,
|
||||||
|
details: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the contribution details from IPFS and sets local instance
|
||||||
|
* properties from it
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
loadDetails(ipfs) {
|
||||||
|
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||||
|
let contributionJSON = JSON.parse(content);
|
||||||
|
let contribution = Ember.Object.create(contributionJSON);
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
kind: contribution.get('kind'),
|
||||||
|
description: contribution.get('description'),
|
||||||
|
url: contribution.get('url')
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO load details
|
||||||
|
// let details = profile.get('accounts');
|
||||||
|
|
||||||
|
Ember.Logger.debug('[proposal] loaded contribution details', contributionJSON);
|
||||||
|
resolve();
|
||||||
|
}).catch((err) => {
|
||||||
|
Ember.Logger.error('[proposal] error trying to load contribution details', this.get('ipfsHash'), err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
+107
-2
@@ -1,14 +1,119 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const {
|
||||||
|
isPresent,
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
|
|
||||||
|
address: null,
|
||||||
|
name: null,
|
||||||
|
kind: null,
|
||||||
|
url: null,
|
||||||
github_username: null,
|
github_username: null,
|
||||||
github_uid: null,
|
github_uid: null,
|
||||||
|
wiki_username: null,
|
||||||
|
ipfsHash: null,
|
||||||
kredits: null,
|
kredits: null,
|
||||||
address: null,
|
isCore: false,
|
||||||
|
isCurrentUser: false,
|
||||||
|
|
||||||
avatarURL: function() {
|
avatarURL: function() {
|
||||||
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
|
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
|
||||||
}.property('github_uid')
|
}.property('github_uid'),
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the contributor's profile data from IPFS and sets local instance
|
||||||
|
* properties from it
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
loadProfile(ipfs) {
|
||||||
|
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||||
|
let profileJSON = JSON.parse(content);
|
||||||
|
let profile = Ember.Object.create(profileJSON);
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
name: profile.get('name'),
|
||||||
|
kind: profile.get('kind')
|
||||||
|
});
|
||||||
|
|
||||||
|
let accounts = profile.get('accounts');
|
||||||
|
let github = accounts.findBy('site', 'github.com');
|
||||||
|
let wiki = accounts.findBy('site', 'wiki.kosmos.org');
|
||||||
|
|
||||||
|
if (isPresent(github)) {
|
||||||
|
this.setProperties({
|
||||||
|
github_username: github.username,
|
||||||
|
github_uid: github.uid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isPresent(wiki)) {
|
||||||
|
this.setProperties({
|
||||||
|
wiki_username: wiki.username
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ember.Logger.debug('[contributor] loaded contributor profile', profile);
|
||||||
|
resolve();
|
||||||
|
}).catch((err) => {
|
||||||
|
Ember.Logger.error('[contributor] error trying to load contributor profile', this.get('ipfsHash'), err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JSON-LD object of the contributor, according to
|
||||||
|
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
toJSON() {
|
||||||
|
let contributor = {
|
||||||
|
"@context": "https://schema.kosmos.org",
|
||||||
|
"@type": "Contributor",
|
||||||
|
"kind": this.get('kind'),
|
||||||
|
"name": this.get('name'),
|
||||||
|
"accounts": []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Ember.isPresent(this.get('url'))) {
|
||||||
|
contributor["url"] = this.get('url');
|
||||||
|
}
|
||||||
|
if (Ember.isPresent(this.get('github_uid'))) {
|
||||||
|
contributor.accounts.push({
|
||||||
|
"site": "github.com",
|
||||||
|
"uid": this.get('github_uid'),
|
||||||
|
"username": this.get('github_username'),
|
||||||
|
"url": `https://github.com/${this.get('github_username')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Ember.isPresent(this.get('wiki_username'))) {
|
||||||
|
contributor.accounts.push({
|
||||||
|
"site": "wiki.kosmos.org",
|
||||||
|
"username": this.get('wiki_username'),
|
||||||
|
"url": `https://wiki.kosmos.org/User:${this.get('wiki_username')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return contributor;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JSON-LD representation of the model as a string
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
serialize() {
|
||||||
|
return JSON.stringify(this.toJSON());
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+90
-3
@@ -1,15 +1,102 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
|
const {
|
||||||
|
isPresent,
|
||||||
|
isEmpty,
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
export default Ember.Object.extend({
|
export default Ember.Object.extend({
|
||||||
|
|
||||||
id: null,
|
id: null,
|
||||||
creator: null,
|
creatorAddress: null,
|
||||||
recipient: null,
|
recipientAddress: null,
|
||||||
|
recipientName: null,
|
||||||
|
recipientProfile: null,
|
||||||
votesCount: null,
|
votesCount: null,
|
||||||
votesNeeded: null,
|
votesNeeded: null,
|
||||||
amount: null,
|
amount: null,
|
||||||
executed: null,
|
executed: null,
|
||||||
|
contribution: null,
|
||||||
|
kind: null,
|
||||||
|
description: null,
|
||||||
url: null,
|
url: null,
|
||||||
ipfsHash: null
|
details: null,
|
||||||
|
ipfsHash: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the contribution details from IPFS and sets local instance
|
||||||
|
* properties from it
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
loadContribution(ipfs) {
|
||||||
|
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
ipfs.getFile(this.get('ipfsHash')).then(content => {
|
||||||
|
let contributionJSON = JSON.parse(content);
|
||||||
|
let contribution = Ember.Object.create(contributionJSON);
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
kind: contribution.get('kind'),
|
||||||
|
description: contribution.get('description'),
|
||||||
|
url: contribution.get('url')
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO load details
|
||||||
|
// let details = profile.get('accounts');
|
||||||
|
|
||||||
|
Ember.Logger.debug('[proposal] loaded contribution details', contributionJSON);
|
||||||
|
resolve();
|
||||||
|
}).catch((err) => {
|
||||||
|
Ember.Logger.error('[proposal] error trying to load contribution details', this.get('ipfsHash'), err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a JSON-LD object of the contribution, according to
|
||||||
|
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
contributionToJSON() {
|
||||||
|
if (isEmpty(this.get('recipientProfile'))) {
|
||||||
|
throw new Error('IPFS hash for recipient profile missing from proposal object');
|
||||||
|
}
|
||||||
|
if (isEmpty(this.get('kind')) || isEmpty(this.get('description'))) {
|
||||||
|
throw new Error('Missing one or more required properties: kind, description');
|
||||||
|
}
|
||||||
|
|
||||||
|
let contribution = {
|
||||||
|
"@context": "https://schema.kosmos.org",
|
||||||
|
"@type": "Contribution",
|
||||||
|
"contributor": {
|
||||||
|
"ipfs": this.get('recipientProfile')
|
||||||
|
},
|
||||||
|
"kind": this.get('kind'),
|
||||||
|
"description": this.get('description'),
|
||||||
|
"details": {}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPresent(this.get('url'))) {
|
||||||
|
contribution["url"] = this.get('url');
|
||||||
|
}
|
||||||
|
|
||||||
|
return contribution;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JSON-LD representation of the contribution as a string
|
||||||
|
*
|
||||||
|
* @method
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
serializeContribution() {
|
||||||
|
return JSON.stringify(this.contributionToJSON());
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ const Router = Ember.Router.extend({
|
|||||||
|
|
||||||
Router.map(function() {
|
Router.map(function() {
|
||||||
this.route('spinner');
|
this.route('spinner');
|
||||||
|
this.route('proposals', function() {
|
||||||
|
this.route('new');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Router;
|
export default Router;
|
||||||
|
|||||||
+19
-3
@@ -1,17 +1,33 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import Contributor from 'kredits-web/models/contributor';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
export default Ember.Route.extend({
|
||||||
|
|
||||||
kredits: Ember.inject.service(),
|
kredits: Ember.inject.service(),
|
||||||
|
|
||||||
|
beforeModel(transition) {
|
||||||
|
const kredits = this.get('kredits');
|
||||||
|
|
||||||
|
if (kredits.get('web3') && kredits.get('web3Provided')) {
|
||||||
|
kredits.get('web3').eth.getAccounts((error, accounts) => {
|
||||||
|
if (error || accounts.length === 0) {
|
||||||
|
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
|
||||||
|
transition.retry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
let kredits = this.get('kredits');
|
let kredits = this.get('kredits');
|
||||||
|
|
||||||
return Ember.RSVP.hash({
|
return Ember.RSVP.hash({
|
||||||
contributors: kredits.getContributors(),
|
contributors: kredits.getContributors(),
|
||||||
totalSupply: kredits.getValueFromContract('totalSupply'),
|
totalSupply: kredits.getValueFromContract('tokenContract', 'totalSupply'),
|
||||||
contributorsCount: kredits.getValueFromContract('contributorsCount'),
|
proposals: kredits.getProposals(),
|
||||||
proposals: kredits.getProposals()
|
contributions: kredits.getContributions(),
|
||||||
|
newContributor: Contributor.create({ kind: 'person' })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
import Proposal from 'kredits-web/models/proposal';
|
||||||
|
|
||||||
|
const {
|
||||||
|
Route,
|
||||||
|
RSVP,
|
||||||
|
inject: {
|
||||||
|
service
|
||||||
|
}
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
|
export default Route.extend({
|
||||||
|
|
||||||
|
kredits: service(),
|
||||||
|
|
||||||
|
model(params) {
|
||||||
|
const proposal = Proposal.create({
|
||||||
|
recipientAddress: params.recipient,
|
||||||
|
amount: params.amount,
|
||||||
|
url: params.url,
|
||||||
|
kind: params.kind || 'dev',
|
||||||
|
ipfsHash: params.ipfsHash
|
||||||
|
});
|
||||||
|
|
||||||
|
return RSVP.hash({
|
||||||
|
proposal,
|
||||||
|
contributors: this.get('kredits').getContributors()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setupController(controller, model) {
|
||||||
|
this._super(controller, model.proposal);
|
||||||
|
|
||||||
|
controller.set('contributors', model.contributors);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
import ipfsAPI from 'npm:ipfs-api';
|
||||||
|
import config from 'kredits-web/config/environment';
|
||||||
|
|
||||||
|
export default Ember.Service.extend({
|
||||||
|
|
||||||
|
ipfsInstance: null,
|
||||||
|
|
||||||
|
ipfs: function() {
|
||||||
|
if (this.get('ipfsInstance')) {
|
||||||
|
return this.get('ipfsInstance');
|
||||||
|
}
|
||||||
|
let ipfs = ipfsAPI(config.ipfs);
|
||||||
|
this.set('ipfsInstance', ipfs);
|
||||||
|
return ipfs;
|
||||||
|
}.property('ipfsInstance'),
|
||||||
|
|
||||||
|
storeFile(content) {
|
||||||
|
let ipfs = this.get('ipfs');
|
||||||
|
return ipfs.add(new ipfs.Buffer(content)).then(res => {
|
||||||
|
Ember.Logger.debug('[ipfs] stored content in IPFS', content, res[0].hash);
|
||||||
|
return res[0].hash;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getFile(hash) {
|
||||||
|
return this.get('ipfs').cat(hash, { buffer: true }).then(res => {
|
||||||
|
return res.toString();
|
||||||
|
}, err => {
|
||||||
|
Ember.Logger.error('[ipfs] error trying to fetch file', hash, err);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
+181
-31
@@ -3,14 +3,27 @@ import Web3 from 'npm:web3';
|
|||||||
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';
|
||||||
|
import kreditsContracts from 'npm:kredits-contracts';
|
||||||
|
import uuid from 'npm:uuid';
|
||||||
|
|
||||||
export default Ember.Service.extend({
|
const {
|
||||||
|
Service,
|
||||||
|
isPresent,
|
||||||
|
inject: {
|
||||||
|
service
|
||||||
|
}
|
||||||
|
} = Ember;
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
|
||||||
|
ipfs: service(),
|
||||||
|
|
||||||
web3Instance: null,
|
web3Instance: null,
|
||||||
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
||||||
|
|
||||||
web3: function() {
|
web3: function() {
|
||||||
if (Ember.isPresent(this.get('web3Instance'))) {
|
if (this.get('web3Instance')) {
|
||||||
return this.get('web3Instance');
|
return this.get('web3Instance');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +35,8 @@ export default Ember.Service.extend({
|
|||||||
this.set('web3Provided', true);
|
this.set('web3Provided', true);
|
||||||
} else {
|
} else {
|
||||||
Ember.Logger.debug('[kredits] Creating new instance from npm module class');
|
Ember.Logger.debug('[kredits] Creating new instance from npm module class');
|
||||||
let provider = new Web3.providers.HttpProvider("http://139.59.248.169:8545");
|
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
|
||||||
|
let provider = new Web3.providers.HttpProvider(providerUrl);
|
||||||
web3Instance = new Web3(provider);
|
web3Instance = new Web3(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,19 +46,53 @@ export default Ember.Service.extend({
|
|||||||
return web3Instance;
|
return web3Instance;
|
||||||
}.property('web3Instance'),
|
}.property('web3Instance'),
|
||||||
|
|
||||||
kreditsContract: function() {
|
currentUserAccounts: function() {
|
||||||
// TODO cache this
|
return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
|
||||||
let contract = this.get('web3')
|
}.property('web3Provided', 'web3'),
|
||||||
.eth.contract(config.kreditsContract.ABI)
|
|
||||||
.at(config.kreditsContract.address);
|
|
||||||
window.Kredits = contract;
|
|
||||||
return contract;
|
|
||||||
}.property('web3'),
|
|
||||||
|
|
||||||
getValueFromContract(contractMethod, ...args) {
|
initializeKreditsContract() {
|
||||||
|
let contract = null;
|
||||||
|
|
||||||
|
if (isPresent(config.contractMetadata)) {
|
||||||
|
if (localStorage.getItem('config:networkId')) {
|
||||||
|
config.contractMetadata['networkId'] = localStorage.getItem('config:networkId');
|
||||||
|
}
|
||||||
|
contract = kreditsContracts(this.get('web3'), config.contractMetadata)['Kredits'];
|
||||||
|
} else {
|
||||||
|
contract = kreditsContracts(this.get('web3'))['Kredits'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract;
|
||||||
|
},
|
||||||
|
|
||||||
|
kreditsContract: function() {
|
||||||
|
if (this.get('kreditsContractInstance')) {
|
||||||
|
return this.get('kreditsContractInstance');
|
||||||
|
}
|
||||||
|
|
||||||
|
let contract = this.initializeKreditsContract();
|
||||||
|
|
||||||
|
this.set('kreditsContractInstance', contract);
|
||||||
|
// window.Kredits = contract;
|
||||||
|
return contract;
|
||||||
|
}.property('kreditsContractInstance', 'web3'),
|
||||||
|
|
||||||
|
tokenContract: function() {
|
||||||
|
if (this.get('tokenContractInstance')) {
|
||||||
|
return this.get('tokenContractInstance');
|
||||||
|
}
|
||||||
|
|
||||||
|
let contract = kreditsContracts(this.get('web3'), config.contractMetadata)['Token'];
|
||||||
|
this.set('tokenContractInstance', contract);
|
||||||
|
window.Token = contract;
|
||||||
|
return contract;
|
||||||
|
}.property('tokenContractInstance', 'web3'),
|
||||||
|
|
||||||
|
getValueFromContract(contract, contractMethod, ...args) {
|
||||||
|
Ember.Logger.debug('[kredits] read from contract', contract);
|
||||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
this.get('kreditsContract')[contractMethod](...args, (err, data) => {
|
this.get(contract)[contractMethod](...args, (err, data) => {
|
||||||
if (err) { reject(err); }
|
if (err) { reject(err); return; }
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -52,18 +100,22 @@ export default Ember.Service.extend({
|
|||||||
|
|
||||||
getContributorData(i) {
|
getContributorData(i) {
|
||||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
this.getValueFromContract('contributorAddresses', i).then(address => {
|
this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => {
|
||||||
this.getValueFromContract('contributors', address).then(person => {
|
this.getValueFromContract('kreditsContract', 'contributors', address).then(person => {
|
||||||
this.getValueFromContract('balanceOf', address).then(balance => {
|
this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => {
|
||||||
|
Ember.Logger.debug('[kredits] person', address, person);
|
||||||
|
|
||||||
let contributor = Contributor.create({
|
let contributor = Contributor.create({
|
||||||
address: address,
|
address: address,
|
||||||
github_username: person[1],
|
ipfsHash: person[2],
|
||||||
github_uid: person[0],
|
kredits: balance.toNumber(),
|
||||||
ipfsHash: person[3],
|
isCurrentUser: this.get('currentUserAccounts').includes(address)
|
||||||
kredits: balance.toNumber()
|
|
||||||
});
|
});
|
||||||
Ember.Logger.debug('[kredits] contributor', contributor);
|
|
||||||
resolve(contributor);
|
contributor.loadProfile(this.get('ipfs')).then(
|
||||||
|
() => resolve(contributor),
|
||||||
|
err => reject(err)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch(err => reject(err));
|
}).catch(err => reject(err));
|
||||||
@@ -72,7 +124,7 @@ export default Ember.Service.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
getContributors() {
|
getContributors() {
|
||||||
return this.getValueFromContract('contributorsCount').then(contributorsCount => {
|
return this.getValueFromContract('kreditsContract', 'contributorsCount').then(contributorsCount => {
|
||||||
let contributors = [];
|
let contributors = [];
|
||||||
|
|
||||||
for(var i = 0; i < contributorsCount.toNumber(); i++) {
|
for(var i = 0; i < contributorsCount.toNumber(); i++) {
|
||||||
@@ -85,7 +137,7 @@ export default Ember.Service.extend({
|
|||||||
|
|
||||||
getProposalData(i) {
|
getProposalData(i) {
|
||||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
this.getValueFromContract('proposals', i).then(p => {
|
this.getValueFromContract('kreditsContract', 'proposals', i).then(p => {
|
||||||
let proposal = Proposal.create({
|
let proposal = Proposal.create({
|
||||||
id : i,
|
id : i,
|
||||||
creatorAddress : p[0],
|
creatorAddress : p[0],
|
||||||
@@ -97,15 +149,23 @@ export default Ember.Service.extend({
|
|||||||
url : p[6],
|
url : p[6],
|
||||||
ipfsHash : p[7]
|
ipfsHash : p[7]
|
||||||
});
|
});
|
||||||
Ember.Logger.debug('[kredits] proposal', proposal);
|
|
||||||
|
if (proposal.get('ipfsHash')) {
|
||||||
|
proposal.loadContribution(this.get('ipfs')).then(
|
||||||
|
() => resolve(proposal),
|
||||||
|
err => reject(err)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Ember.Logger.warn('[kredits] proposal from blockchain is missing IPFS hash', proposal);
|
||||||
resolve(proposal);
|
resolve(proposal);
|
||||||
|
}
|
||||||
}).catch(err => reject(err));
|
}).catch(err => reject(err));
|
||||||
});
|
});
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
getProposals() {
|
getProposals() {
|
||||||
return this.getValueFromContract('proposalsCount').then(proposalsCount => {
|
return this.getValueFromContract('kreditsContract', 'proposalsCount').then(proposalsCount => {
|
||||||
let proposals = [];
|
let proposals = [];
|
||||||
|
|
||||||
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
||||||
@@ -120,15 +180,105 @@ export default Ember.Service.extend({
|
|||||||
Ember.Logger.debug('[kredits] vote for', proposalId);
|
Ember.Logger.debug('[kredits] vote for', proposalId);
|
||||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
||||||
if (err) { reject(err); }
|
if (err) { reject(err); return; }
|
||||||
Ember.Logger.debug('[kredits] vote response', data);
|
Ember.Logger.debug('[kredits] vote response', data);
|
||||||
resolve(data);
|
resolve(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
logKreditsContract: function() {
|
addContributor(contributor) {
|
||||||
Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
Ember.Logger.debug('[kredits] add contributor', contributor);
|
||||||
}.on('init')
|
|
||||||
|
contributor.setProperties({
|
||||||
|
kredits: 0,
|
||||||
|
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
|
||||||
|
});
|
||||||
|
|
||||||
|
let id = uuid.v4();
|
||||||
|
|
||||||
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
this.get('ipfs').storeFile(contributor.serialize()).then(ipfsHash => {
|
||||||
|
contributor.set('ipfsHash', ipfsHash);
|
||||||
|
this.get('kreditsContract').addContributor(contributor.address, contributor.name, contributor.ipfsHash, contributor.isCore, id, (err, data) => {
|
||||||
|
if (err) { reject(err); return; }
|
||||||
|
Ember.Logger.debug('[kredits] add contributor response', data);
|
||||||
|
resolve(contributor);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addProposal(proposal) {
|
||||||
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
const {
|
||||||
|
recipientAddress,
|
||||||
|
amount,
|
||||||
|
url
|
||||||
|
} = proposal.getProperties('recipientAddress', 'amount', 'url');
|
||||||
|
|
||||||
|
this.get('ipfs').storeFile(proposal.serializeContribution()).then(ipfsHash => {
|
||||||
|
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => {
|
||||||
|
if (err) { reject(err); return; }
|
||||||
|
Ember.Logger.debug('[kredits] add proposal response', data);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getTokenMintedEvents(fromBlock=0, toBlock='latest') {
|
||||||
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
const minted = this.get('tokenContract')
|
||||||
|
.Minted({}, {fromBlock: fromBlock, toBlock: toBlock});
|
||||||
|
|
||||||
|
minted.get((err, res) => {
|
||||||
|
if (err) {
|
||||||
|
Ember.Logger.error('[kredits] Error reading Token#Minted events', res);
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ember.Logger.warn('[kredits] Token#Minted events', res);
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getContributions() {
|
||||||
|
// this.getTokenMintedEvents().then(events => {
|
||||||
|
let contributions = [];
|
||||||
|
|
||||||
|
config.fixtures.mintedEvents.forEach(mintedEvent => {
|
||||||
|
contributions.push(this.getContributionData(mintedEvent));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ember.RSVP.all(contributions);
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
|
getContributionData(mintedEvent) {
|
||||||
|
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||||
|
let contribution = Contribution.create({
|
||||||
|
recipientAddress: mintedEvent.args.recipientAddress,
|
||||||
|
contributorId: mintedEvent.args.contributorId,
|
||||||
|
amount: mintedEvent.args.amount,
|
||||||
|
ipfsHash: mintedEvent.args.reference
|
||||||
|
});
|
||||||
|
|
||||||
|
if (contribution.get('ipfsHash')) {
|
||||||
|
contribution.loadDetails(this.get('ipfs')).then(
|
||||||
|
() => resolve(contribution),
|
||||||
|
err => reject(err)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Ember.Logger.warn('[kredits] contribution from Minted event is missing IPFS hash', contribution);
|
||||||
|
resolve(contribution);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// logKreditsContract: function() {
|
||||||
|
// Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
||||||
|
// }.on('init')
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
$blue: #68d7fb;
|
||||||
|
$purple: #8f68fb;
|
||||||
|
$pink: #e068fb;
|
||||||
|
$green: #97fb68;
|
||||||
|
$yellow: #fbe468;
|
||||||
|
$red: #fb6868;
|
||||||
|
|
||||||
|
$primaryColor: $blue;
|
||||||
+22
-4
@@ -15,6 +15,7 @@
|
|||||||
@import "neat";
|
@import "neat";
|
||||||
@import "settings/breakpoints";
|
@import "settings/breakpoints";
|
||||||
@import "layout";
|
@import "layout";
|
||||||
|
@import "colors";
|
||||||
|
|
||||||
$font-family-sans: 'Open Sans', sans-serif;
|
$font-family-sans: 'Open Sans', sans-serif;
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ body {
|
|||||||
background-repeat: none;
|
background-repeat: none;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
font-family: $font-family-sans;
|
font-family: $font-family-sans;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,7 @@ h1, h2, h3, h4, h5, input, button {
|
|||||||
section {
|
section {
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 2.8rem;
|
font-size: 2.8rem;
|
||||||
color: lightblue;
|
color: $primaryColor;
|
||||||
@include media($mobile) {
|
@include media($mobile) {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,7 @@ section {
|
|||||||
p.stats {
|
p.stats {
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: lightblue;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
span.number {
|
span.number {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -65,13 +66,29 @@ section {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&#proposals-open, &#proposals-closed {
|
||||||
|
.actions {
|
||||||
|
padding-top: 3rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: $primaryColor;
|
||||||
|
text-align: center;
|
||||||
|
@include media($mobile) {
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primaryColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input[type=submit] {
|
button, input[type=submit] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px solid rgba(22, 21, 40, 1);
|
border: 1px solid rgba(22, 21, 40, 1);
|
||||||
background-color: rgba(22, 21, 40, 0.6);
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
color: lightblue;
|
color: $primaryColor;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -87,3 +104,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";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
section#add-contributor {
|
section#add-contributor, section#add-proposal {
|
||||||
|
|
||||||
form {
|
form {
|
||||||
|
|
||||||
@@ -8,10 +8,14 @@ section#add-contributor {
|
|||||||
&.actions {
|
&.actions {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
a {
|
||||||
|
color: $primaryColor;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=text] {
|
input[type=text], select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -22,10 +26,67 @@ section#add-contributor {
|
|||||||
&:focus, &.valid {
|
&:focus, &.valid {
|
||||||
background-color: rgba(22, 21, 40, 0.6);
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
}
|
}
|
||||||
|
@include placeholder {
|
||||||
|
color: rgba(238, 238, 238, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
|
background-image:
|
||||||
|
linear-gradient(45deg, transparent 50%, gray 50%),
|
||||||
|
linear-gradient(135deg, gray 50%, transparent 50%);
|
||||||
|
background-position:
|
||||||
|
calc(100% - 1.5rem) calc(1rem + 0.5rem),
|
||||||
|
calc(100% - 1rem) calc(1rem + 0.5rem);
|
||||||
|
background-size:
|
||||||
|
0.5rem 0.5rem,
|
||||||
|
0.5rem 0.5rem;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
&:invalid {
|
||||||
|
color: rgba(238, 238, 238, 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=submit] {
|
input[type=submit] {
|
||||||
padding: 0.6rem 2rem;
|
padding: 0.6rem 2rem;
|
||||||
|
&:disabled {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.checkbox {
|
||||||
|
line-height: 3.2rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
width: 3.2rem;
|
||||||
|
font-size: 2rem;
|
||||||
|
background-color: rgba(22, 21, 40, 0.3);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
content: '✓';
|
||||||
|
color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox]:checked + label.checkbox {
|
||||||
|
&::before {
|
||||||
|
background-color: rgba(22, 21, 40, 0.6);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
ul.contribution-list {
|
||||||
|
clear: both;
|
||||||
|
width: 100%;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
// display: block;
|
||||||
|
padding: 0 1.2rem;
|
||||||
|
line-height: 4.2rem;
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||||
|
&:first-of-type {
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,13 +7,54 @@ table.contributor-list {
|
|||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
border-top: 1px solid rgba(255,255,255,0.2);
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
}
|
}
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
|
||||||
|
&.current-user {
|
||||||
|
background-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.metadata {
|
||||||
|
height: 0;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $primaryColor;
|
||||||
|
&:hover, &:active {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 0;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline;
|
||||||
|
&+li {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
height: auto;
|
||||||
|
ul {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
padding: 0 1.2rem;
|
padding: 0 1.2rem;
|
||||||
line-height: 4.2rem;
|
line-height: 4.2rem;
|
||||||
|
|
||||||
background-color: rgba(255,255,255,0.1);
|
|
||||||
&.person {
|
&.person {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
@@ -25,6 +66,7 @@ table.contributor-list {
|
|||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.kredits {
|
&.kredits {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.amount {
|
.amount {
|
||||||
@@ -37,3 +79,4 @@ table.contributor-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
margin-top: 12rem;
|
margin-top: 12rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
color: lightblue;
|
color: $primaryColor;
|
||||||
|
|
||||||
@include media($mobile) {
|
@include media($mobile) {
|
||||||
margin-top: 6rem;
|
margin-top: 6rem;
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
#path-comet {
|
#path-comet {
|
||||||
fill: lightblue;
|
fill: $primaryColor;
|
||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
|
|
||||||
animation-name: pulse;
|
animation-name: pulse;
|
||||||
|
|||||||
@@ -14,9 +14,14 @@ ul.proposal-list {
|
|||||||
border-top: 1px solid rgba(255,255,255,0.2);
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.id {
|
.category {
|
||||||
color: lightblue;
|
color: $blue;
|
||||||
padding-right: 0.2rem;
|
padding-right: 0.2rem;
|
||||||
|
&.community { color: $red; }
|
||||||
|
&.dev { color: $pink; }
|
||||||
|
&.design { color: $yellow; }
|
||||||
|
&.docs { color: $green; }
|
||||||
|
&.ops { color: $purple; }
|
||||||
}
|
}
|
||||||
.amount {
|
.amount {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -31,8 +36,8 @@ ul.proposal-list {
|
|||||||
}
|
}
|
||||||
.votes {
|
.votes {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: lightblue;
|
color: $primaryColor;
|
||||||
padding-left: 1rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|||||||
+17
-8
@@ -8,29 +8,37 @@
|
|||||||
|
|
||||||
<p class="stats">
|
<p class="stats">
|
||||||
<span class="number">{{model.totalSupply}}</span> kredits issued and distributed among
|
<span class="number">{{model.totalSupply}}</span> kredits issued and distributed among
|
||||||
<span class="number">{{model.contributorsCount}}</span> contributors.
|
<span class="number">{{contributorsWithKredits.length}}</span> contributors.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{{#if contractInteractionEnabled}}
|
|
||||||
{{#if proposalsOpen}}
|
{{#if proposalsOpen}}
|
||||||
<section id="proposals-open">
|
<section id="proposals-open">
|
||||||
<header>
|
<header>
|
||||||
<h2>Open Proposals</h2>
|
<h2>Open Proposals</h2>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{proposal-list proposals=proposalsOpenSorted confirmAction="confirmProposal"}}
|
{{proposal-list proposals=proposalsOpenSorted
|
||||||
|
confirmAction="confirmProposal"
|
||||||
|
contractInteractionEnabled=contractInteractionEnabled}}
|
||||||
|
|
||||||
|
{{#if contractInteractionEnabled}}
|
||||||
|
<p class="actions">
|
||||||
|
{{#link-to 'proposals.new'}}Create new proposal{{/link-to}}
|
||||||
|
</p>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<section id="proposals-closed">
|
<section id="contributions">
|
||||||
<header>
|
<header>
|
||||||
<h2>Closed Proposals</h2>
|
<h2>Contributions</h2>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{proposal-list proposals=proposalsClosedSorted confirmAction="confirmProposal"}}
|
{{contribution-list contributions=model.contributions
|
||||||
|
contributors=model.contributors}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -40,7 +48,8 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{add-contributor kredits=kredits}}
|
{{add-contributor contributors=model.contributors
|
||||||
|
newContributor=model.newContributor
|
||||||
|
contractInteractionEnabled=contractInteractionEnabled}}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{{/if}}
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<section id="add-proposal">
|
||||||
|
<header>
|
||||||
|
<h2>Add Proposal</h2>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{{add-proposal proposal=model contributors=contributors onSave=(action 'onSave')}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
+38
-4
@@ -1,5 +1,5 @@
|
|||||||
/* jshint node: true */
|
/* jshint node: true */
|
||||||
let contracts = require('../vendor/contract-abis');
|
const fs = require('fs');
|
||||||
|
|
||||||
module.exports = function(environment) {
|
module.exports = function(environment) {
|
||||||
var ENV = {
|
var ENV = {
|
||||||
@@ -7,6 +7,7 @@ module.exports = function(environment) {
|
|||||||
environment: environment,
|
environment: environment,
|
||||||
rootURL: '/',
|
rootURL: '/',
|
||||||
locationType: 'auto',
|
locationType: 'auto',
|
||||||
|
|
||||||
EmberENV: {
|
EmberENV: {
|
||||||
FEATURES: {
|
FEATURES: {
|
||||||
// Here you can enable experimental features on an ember canary build
|
// Here you can enable experimental features on an ember canary build
|
||||||
@@ -23,18 +24,51 @@ module.exports = function(environment) {
|
|||||||
// when it is created
|
// when it is created
|
||||||
},
|
},
|
||||||
|
|
||||||
kreditsContract: {
|
browserify: {
|
||||||
ABI: contracts['Kredits'].abi
|
tests: true,
|
||||||
|
transform: [
|
||||||
|
["babelify", {
|
||||||
|
presets: ["es2015"],
|
||||||
|
global: true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
contractMetadata: {},
|
||||||
|
|
||||||
|
web3ProviderUrl: 'https://parity.kosmos.org:8545',
|
||||||
|
|
||||||
|
ipfs: {
|
||||||
|
host: 'ipfs.kosmos.org',
|
||||||
|
port: '5444',
|
||||||
|
protocol: 'https'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.KREDITS_CONTRACT_ADDR) {
|
||||||
|
ENV.contractMetadata['Kredits'] = { address: process.env.KREDITS_CONTRACT_ADDR };
|
||||||
|
}
|
||||||
|
if (process.env.TOKEN_CONTRACT_ADDR) {
|
||||||
|
ENV.contractMetadata['Token'] = { address: process.env.TOKEN_CONTRACT_ADDR };
|
||||||
|
}
|
||||||
|
if (process.env.WEB3_PROVIDER_URL) {
|
||||||
|
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||||
|
}
|
||||||
|
ENV.contractMetadata['networkId'] = "17";
|
||||||
|
|
||||||
if (environment === 'development') {
|
if (environment === 'development') {
|
||||||
ENV.kreditsContract.address = '0x9C68Af50e97f5605402B4C01e7aB836ed7145e8B';
|
|
||||||
// ENV.APP.LOG_RESOLVER = true;
|
// ENV.APP.LOG_RESOLVER = true;
|
||||||
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||||
// ENV.APP.LOG_TRANSITIONS = true;
|
// ENV.APP.LOG_TRANSITIONS = true;
|
||||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||||
|
ENV.ipfs = {
|
||||||
|
host: 'localhost',
|
||||||
|
port: '5001',
|
||||||
|
protocol: 'http'
|
||||||
|
};
|
||||||
|
const mintedEvents = JSON.parse(fs.readFileSync('config/fixtures-token-minted.json'));
|
||||||
|
ENV.fixtures = { mintedEvents: mintedEvents };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment === 'test') {
|
if (environment === 'test') {
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x70ba4415fc788d9481b5c2e557a55ce698de15b02eb5fba514a0b33027796c5a",
|
||||||
|
"blockNumber": 46,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x3d4baea1f8a63f5b80491d5e67f2a833a4a6db7427a0c07ca181a852cd29dedb",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "150",
|
||||||
|
"reference": "QmWj7XC6sSu1D7zmcH1tHFV9711P96m1z4qFP8yALZMWem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x0112ca39f27dbc17ec7478336a9ba63ede0930cad0ead68b6fd441269950d6cc",
|
||||||
|
"blockNumber": 47,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x8d1277270c51afdb9b6345015de176260412d7a649dfb00307dc8e7c513a250e",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||||
|
"contributorId": 2,
|
||||||
|
"amount": "150",
|
||||||
|
"reference": "QmSfgMxbBHNouW9vwD8DHkfdbMjAuMpv1fpBB6uPCazRCA"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x98fc834495394b8b26520fcd503508b953f7977a89df49a2a2d6711f2aa4d0c6",
|
||||||
|
"blockNumber": 48,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x8f21b1df6ea33520c437bf9252a611603f52dca397ba93a3e2680da2c31d28e1",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "1000",
|
||||||
|
"reference": "QmNbmsh3Rozksv6a5cTQM5mapQNpksU4Zcy9zMLcWsJoUf"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xf34ce94de4496441cb3972525f4010370b6957bbc09deba15501a169d063fa59",
|
||||||
|
"blockNumber": 50,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x328ffbe2b34ba80c861445768b6069bc43eb511e196e7c22042a1f714aad78fa",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||||
|
"contributorId": 3,
|
||||||
|
"amount": "150",
|
||||||
|
"reference": "QmYE2ipsfbk9fwLJQ5wBrc9WKtneHjEGfV2RfngsxdKe1g"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xf6f8c1686497c7dc0ae4af2c87d7547b75bdffb960dce52a62bf74c1976980fb",
|
||||||
|
"blockNumber": 51,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x694796b4b1bd82cadea6471eb2f718323ca85fed00ddb9b8a73524dec31141e6",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||||
|
"contributorId": 2,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmZZVbZN6hQTA5ZxrUywehdC2cCHkD6wvkVbVWJP21UmgR"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x771045eecdd57959e9cb9ac613df988eeee8b800e8f9edebfdf01a8f1a4f3c5d",
|
||||||
|
"blockNumber": 52,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xaf9f073f80b8b492a7e822cfd38e5d13474a99aaef9be0c3bc0ffaf8c653a5f4",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmbrszXGexzCZaWer6nFxjj4EXp5y4WT3WYPrbeNooQMyy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x86ee58b0e36f3d199b0d241f3efb868a7276fdf084cff48ef045bdfe235c0506",
|
||||||
|
"blockNumber": 53,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x9c7076b2fa49988fe706639376413bc79e4c5549c0145f00d7f27af6088c7128",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "150",
|
||||||
|
"reference": "QmV5wSKLRBdXXbRuxWneQ1tDZP75Dpjv3adQHGye8CemEb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xc2b3f860e0b8d4037529eca9b7d539332b3cc08741165378ac8368c22c6a18b3",
|
||||||
|
"blockNumber": 54,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xfd2f5439e265e8899be5e2e18a9df56f4706e618d5459aa8f206aec091f7c0d9",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||||
|
"contributorId": 2,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmNp6ggpaR3yNuEWZbCttnUBjshQonFWE766iyVLAKfq7M"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x860667b8f67ae22ac97bd67d9ccb245eb98312eb5cb22303d1a8a383defbb5fc",
|
||||||
|
"blockNumber": 60,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xbe153f64a31a1669ef1bffa16b0aeb1657b1b9fe779c38f6455d05fe3d96fd5b",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmY2eBgBtFir4edYbw91KQNidMNHnCCem2LCJpTJ56Q87U"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x2c5da053f98cb5462a8aa4fa6bb2a5e7bb324baa404fbbd8ea25fdaaaac1b0db",
|
||||||
|
"blockNumber": 65,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x1df5a2819a6312d170c323b517b03a7186a789845ecfe896d37de6fa482b924c",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||||
|
"contributorId": 3,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmeVbzGiXmPz4qgi5PfktZi2Esb4EVPQZLRnWnUPVatJgF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x0e478ae9cf6e09e2ca8c4413c874f39decfb9d51c976467ef14d99efa79c9bcb",
|
||||||
|
"blockNumber": 75,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xf16ace4f72c599c3b738bdaa4518f59a47dcdc304a8f24308c5d121ae0ae5b97",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x0536806df512d6cdde913cf95c9886f65b1d3462",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "100",
|
||||||
|
"reference": "QmS3cywqGhHh6QBLnyBWXPTas2UnHEW8ZV8zZy6pVw95Zx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0x31e91da869e9b87949e75ee14879005b172df96267f9ffd76d5024a46fe48a3c",
|
||||||
|
"blockNumber": 76,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x735eb48c81f6868b886d3afede6b031323e78b221f3e2653ad2186c3e4e1abe9",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac",
|
||||||
|
"contributorId": 2,
|
||||||
|
"amount": "500",
|
||||||
|
"reference": "QmP5RPzjgRdtq9Uj6tXxgMfA1GkY9Fh5ghXpDYBtc9YwMp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xd41a4c3caae9e52632eba047d038e430b50920138e3a625b50a0a887e719a487",
|
||||||
|
"blockNumber": 77,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x075beb007d18b38344599fe5b4dc315633aebedf08201ab9537f7d407811b0a2",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||||
|
"contributorId": 2,
|
||||||
|
"amount": "1000",
|
||||||
|
"reference": "QmTxwugByKfyPX2czyVDGhXK3NLZkA3uh2DbNnaAMVrVJB"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xcb3863eb681873a2c5954e07e085d4004a2824fcd17364b647e2fff1bea3ad4b",
|
||||||
|
"blockNumber": 78,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xdf680c7a0bc60ef02619def582f68230bdd0fd8a945c9006841212ebcd6b80db",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||||
|
"contributorId": 3,
|
||||||
|
"amount": "300",
|
||||||
|
"reference": "QmYXyGSK6RyXQHNPHAADjpJ4P3mrz5Lf1bNnrp2yRaDxNo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xe322d5292506ef1c4c915e875a4e8c1882d154fd9b711522809045bbb884e11d",
|
||||||
|
"blockNumber": 89,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x5d90bf8c74b18b602e8b04fbca8678033589f6527b1ced08c0ee7d6c96f71949",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xb2d955733e6a470533f68f72d0af442070f24f55",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "300",
|
||||||
|
"reference": "QmZdUuq4NMz2S5bD3m9h9MNNsbTVeFVHzQss4uS1ZqPLsw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xbb84cfc9c3298b045d4ae70afa9cc966db578b00e897f3d6988b08deeca0e3d9",
|
||||||
|
"blockNumber": 120,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0xf91a038b17cfd9b5eac8f4474d5e4d9a5034dead51fc2fffc9e88afdefd9129c",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0xf18e631ea191ae4ebe70046fcb01a43655441c6d",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "500",
|
||||||
|
"reference": "QmdQWivxwjuEHEYVu3ezZCzEWps6wEvQ8ePUHbQ7htSN8V"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "0x63770f81fbe0d2b4159b6752eda99ee7c44ca5e4",
|
||||||
|
"blockHash": "0xc85a520ffc16b5f84d5f5bd29478d0871a045356e598c72be9a44ae3e6826bb5",
|
||||||
|
"blockNumber": 121,
|
||||||
|
"logIndex": 0,
|
||||||
|
"transactionHash": "0x59a2e3e1f81b8113b3b71eba26f28f75ba18d60b6dda6a4a216d566d7d68a31d",
|
||||||
|
"transactionIndex": 0,
|
||||||
|
"transactionLogIndex": "0x0",
|
||||||
|
"type": "mined",
|
||||||
|
"event": "Minted",
|
||||||
|
"args": {
|
||||||
|
"recipientAddress": "0x805bf817be0e53bf618eb41ae0775ac314f989a1",
|
||||||
|
"contributorId": 1,
|
||||||
|
"amount": "50",
|
||||||
|
"reference": "QmfDG3vCAgQsqkxkb6jCnLqw11wjw3uUpiZRbRBLZ9GpYi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
+31
-7
@@ -1,20 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Small description for kredits-web goes here",
|
"description": "Contribution dashboard of the Kosmos project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "",
|
"author": "Kosmos Contributors <mail@kosmos.org>",
|
||||||
|
"contributors": [
|
||||||
|
"Garret Alfert <alfert@wevelop.de>",
|
||||||
|
"Sebastian Kippe <sebastian@kip.pe>",
|
||||||
|
"Michael Bumann <hello@michaelbumann.com>"
|
||||||
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"doc": "doc",
|
"doc": "doc",
|
||||||
"test": "tests"
|
"test": "tests"
|
||||||
},
|
},
|
||||||
"repository": "",
|
"repository": "https://github.com/67P/kredits-web",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ember build",
|
|
||||||
"start": "ember server",
|
"start": "ember server",
|
||||||
"test": "ember test"
|
"start-local": "WEB3_PROVIDER_URL=http://localhost:8545 KREDITS_PROVIDER_URL=http://localhost:8545 KREDITS_CONTRACT_ADDR=`kredits-inspect -c Kredits -r -w address` TOKEN_CONTRACT_ADDR=`kredits-inspect -c Token -r -w address` npm start",
|
||||||
|
"test": "ember test",
|
||||||
|
"postinstall": "bower install",
|
||||||
|
"build": "ember build",
|
||||||
|
"build-prod": "ember build --environment production",
|
||||||
|
"update-version-file": "bash scripts/update-version-file.sh",
|
||||||
|
"deploy": "npm run build-prod && npm run update-version-file && bash scripts/deploy.sh",
|
||||||
|
"kredits-console": "kredits-console",
|
||||||
|
"kredits-parity": "kredits-parity",
|
||||||
|
"kredits-deploy": "kredits-deploy",
|
||||||
|
"kredits-inspect": "kredits-inspect",
|
||||||
|
"kredits-seed": "kredits-seed"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-preset-es2015": "^6.22.0",
|
||||||
|
"babelify": "^7.3.0",
|
||||||
"broccoli-asset-rev": "^2.4.5",
|
"broccoli-asset-rev": "^2.4.5",
|
||||||
"ember-ajax": "^2.4.1",
|
"ember-ajax": "^2.4.1",
|
||||||
"ember-browserify": "^1.1.13",
|
"ember-browserify": "^1.1.13",
|
||||||
@@ -28,14 +45,21 @@
|
|||||||
"ember-cli-jshint": "^2.0.1",
|
"ember-cli-jshint": "^2.0.1",
|
||||||
"ember-cli-qunit": "^3.0.1",
|
"ember-cli-qunit": "^3.0.1",
|
||||||
"ember-cli-release": "^0.2.9",
|
"ember-cli-release": "^0.2.9",
|
||||||
"ember-cli-sass": "6.1.1",
|
"ember-cli-sass": "^7.0.0",
|
||||||
"ember-cli-sri": "^2.1.0",
|
"ember-cli-sri": "^2.1.0",
|
||||||
"ember-cli-test-loader": "^1.1.0",
|
"ember-cli-test-loader": "^1.1.0",
|
||||||
"ember-cli-uglify": "^1.2.0",
|
"ember-cli-uglify": "^1.2.0",
|
||||||
"ember-export-application-global": "^1.0.5",
|
"ember-export-application-global": "^1.0.5",
|
||||||
"ember-load-initializers": "^0.5.1",
|
"ember-load-initializers": "^0.5.1",
|
||||||
|
"ember-parachute": "0.1.0",
|
||||||
"ember-resolver": "^2.0.3",
|
"ember-resolver": "^2.0.3",
|
||||||
|
"ember-truth-helpers": "1.3.0",
|
||||||
|
"ipfs-api": "^12.1.7",
|
||||||
|
"kosmos-schemas": "~1.1.2",
|
||||||
|
"kredits-contracts": "^2.4.0",
|
||||||
"loader.js": "^4.0.10",
|
"loader.js": "^4.0.10",
|
||||||
|
"tv4": "^1.3.0",
|
||||||
|
"uuid": "^3.0.1",
|
||||||
"web3": "^0.18.2"
|
"web3": "^0.18.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
# Get revision from local master
|
||||||
|
rev=`git rev-parse HEAD`
|
||||||
|
short_rev=${rev:0:7}
|
||||||
|
# Check out build branch
|
||||||
|
git checkout build-production
|
||||||
|
# Copy from build dir to root
|
||||||
|
cp -r dist/* .
|
||||||
|
# Add all files
|
||||||
|
git add --all
|
||||||
|
# Commit build files
|
||||||
|
git commit -m "Build production from $short_rev"
|
||||||
|
# Push to remote
|
||||||
|
git push 5apps build-production:master
|
||||||
|
# Push build branch to collab repo
|
||||||
|
git push origin build-production:build-production
|
||||||
|
# Go back to previous branch
|
||||||
|
git checkout -
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PACKAGE_VERSION=$(cat package.json \
|
||||||
|
| grep version \
|
||||||
|
| head -1 \
|
||||||
|
| awk -F: '{ print $2 }' \
|
||||||
|
| sed 's/[",]//g' \
|
||||||
|
| tr -d '[[:space:]]')
|
||||||
|
|
||||||
|
echo $PACKAGE_VERSION > dist/VERSION
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import { moduleForComponent, test } from 'ember-qunit';
|
// import { moduleForComponent, test } from 'ember-qunit';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
// import hbs from 'htmlbars-inline-precompile';
|
||||||
|
//
|
||||||
moduleForComponent('add-contributor', 'Integration | Component | add contributor', {
|
// moduleForComponent('add-contributor', 'Integration | Component | add contributor', {
|
||||||
integration: true
|
// integration: true
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
test('it renders', function(assert) {
|
// test('it renders', function(assert) {
|
||||||
|
//
|
||||||
// Set any properties with this.set('myProperty', 'value');
|
// // Set any properties with this.set('myProperty', 'value');
|
||||||
// Handle any actions with this.on('myAction', function(val) { ... });
|
// // Handle any actions with this.on('myAction', function(val) { ... });
|
||||||
|
//
|
||||||
this.render(hbs`{{add-contributor}}`);
|
// this.render(hbs`{{add-contributor}}`);
|
||||||
|
//
|
||||||
assert.equal(this.$().text().trim(), '');
|
// assert.equal(this.$().text().trim(), '');
|
||||||
});
|
// });
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { moduleForComponent, test } from 'ember-qunit';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
moduleForComponent('add-proposal', 'Integration | Component | add proposal', {
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders', function(assert) {
|
||||||
|
|
||||||
|
// Set any properties with this.set('myProperty', 'value');
|
||||||
|
// Handle any actions with this.on('myAction', function(val) { ... });
|
||||||
|
|
||||||
|
this.render(hbs`{{add-proposal}}`);
|
||||||
|
|
||||||
|
assert.equal(this.$('.actions a').text().trim(), 'Back');
|
||||||
|
});
|
||||||
@@ -1,11 +1,38 @@
|
|||||||
// import { moduleFor, test } from 'ember-qunit';
|
import Ember from 'ember';
|
||||||
//
|
import Contributor from 'kredits-web/models/contributor';
|
||||||
// moduleFor('controller:index', 'Unit | Controller | index');
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
|
||||||
// let addFixtures = function(controller) {
|
const {
|
||||||
// controller.set('model', { contributors: [
|
isPresent,
|
||||||
// { github_username: "neo", github_uid: "318", kredits: 10000 },
|
isEmpty
|
||||||
// { github_username: "morpheus", github_uid: "843", kredits: 15000 },
|
} = Ember;
|
||||||
// { github_username: "mouse", github_uid: "842", kredits: 5000 },
|
|
||||||
// ]});
|
moduleFor('controller:index', 'Unit | Controller | index', {
|
||||||
// };
|
needs: ['service:ipfs', 'service:kredits']
|
||||||
|
});
|
||||||
|
|
||||||
|
let addFixtures = function(controller) {
|
||||||
|
controller.set('model', {
|
||||||
|
contributors: [],
|
||||||
|
proposals: []
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
{ github_username: "neo", github_uid: "318", kredits: 10000 },
|
||||||
|
{ github_username: "morpheus", github_uid: "843", kredits: 15000 },
|
||||||
|
{ github_username: "trinity", github_uid: "123", kredits: 5000 },
|
||||||
|
{ github_username: "mouse", github_uid: "696", kredits: 0 }
|
||||||
|
].forEach(fixture => {
|
||||||
|
controller.get('model.contributors').push(Contributor.create(fixture));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
test('doesn\'t contain people with 0 kredits', function(assert) {
|
||||||
|
let controller = this.subject();
|
||||||
|
addFixtures(controller);
|
||||||
|
|
||||||
|
let contributorsSorted = controller.get('contributorsSorted');
|
||||||
|
|
||||||
|
assert.ok(isPresent(contributorsSorted.findBy('github_username', 'neo')));
|
||||||
|
assert.ok(isEmpty(contributorsSorted.findBy('github_username', 'mouse')));
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
|
||||||
|
moduleFor('controller:proposals/new', 'Unit | Controller | proposals/new', {
|
||||||
|
// Specify the other units that are required for this test.
|
||||||
|
// needs: ['controller:foo']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace this with your real tests.
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let controller = this.subject();
|
||||||
|
assert.ok(controller);
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
// import schemas from 'npm:kosmos-schemas';
|
||||||
|
// import tv4 from 'npm:tv4';
|
||||||
|
|
||||||
|
moduleFor('model:contribution', 'Unit | Model | contribution', {
|
||||||
|
// Specify the other units that are required for this test.
|
||||||
|
needs: []
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let model = this.subject();
|
||||||
|
// let store = this.store();
|
||||||
|
assert.ok(!!model);
|
||||||
|
});
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
import { moduleFor, test } from 'ember-qunit';
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
import schemas from 'npm:kosmos-schemas';
|
||||||
|
import tv4 from 'npm:tv4';
|
||||||
|
|
||||||
moduleFor('model:contributor', 'Unit | Model | contributor');
|
moduleFor('model:contributor', 'Unit | Model | contributor');
|
||||||
|
|
||||||
test('avatarURL returns correct URL', function(assert) {
|
test('#avatarURL() returns correct URL', function(assert) {
|
||||||
let model = this.subject();
|
let model = this.subject();
|
||||||
model.set('github_uid', '318');
|
model.set('github_uid', '318');
|
||||||
|
|
||||||
assert.equal(model.get('avatarURL'), 'https://avatars2.githubusercontent.com/u/318?v=3&s=128');
|
assert.equal(model.get('avatarURL'), 'https://avatars2.githubusercontent.com/u/318?v=3&s=128');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#toJSON() returns a valid JSON-LD representation of the model', function(assert) {
|
||||||
|
let model = this.subject();
|
||||||
|
|
||||||
|
model.setProperties({
|
||||||
|
name: 'Satoshi Nakamoto',
|
||||||
|
kind: 'person',
|
||||||
|
github_uid: 123,
|
||||||
|
github_username: 'therealsatoshi',
|
||||||
|
wiki_username: 'Satoshi',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(tv4.validate(model.toJSON(), schemas['contributor']));
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,49 @@
|
|||||||
import { moduleFor, test } from 'ember-qunit';
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
import schemas from 'npm:kosmos-schemas';
|
||||||
|
import tv4 from 'npm:tv4';
|
||||||
|
|
||||||
moduleFor('model:proposal', 'Unit | Model | proposal');
|
moduleFor('model:proposal', 'Unit | Model | proposal');
|
||||||
|
|
||||||
test('it exists', function(assert) {
|
test('#toJSON() requires a recipient profile IPFS hash to be set', function(assert) {
|
||||||
let model = this.subject();
|
let model = this.subject();
|
||||||
// let store = this.store();
|
|
||||||
assert.ok(!!model);
|
model.setProperties({
|
||||||
|
recipientAddress: '0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let json = model.contributionToJSON();
|
||||||
|
assert.notOk(json, true);
|
||||||
|
} catch(e) {
|
||||||
|
assert.ok(e.message.match(/IPFS hash .* missing/i));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#toJSON() requires kind and description to be set', function(assert) {
|
||||||
|
let model = this.subject();
|
||||||
|
|
||||||
|
model.setProperties({
|
||||||
|
recipientProfile: 'QmT2A7rY4e7uoKktkcFHQNN7BD1oXdZTgd8wNkr1u9nNVE'
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let json = model.contributionToJSON();
|
||||||
|
assert.notOk(json, true);
|
||||||
|
} catch(e) {
|
||||||
|
assert.ok(e.message.match(/Missing .* kind.*description/i));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#toJSON() returns a valid JSON-LD representation of the model', function(assert) {
|
||||||
|
let model = this.subject();
|
||||||
|
|
||||||
|
model.setProperties({
|
||||||
|
recipientAddress: '0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac',
|
||||||
|
kind: 'design',
|
||||||
|
description: 'New logo design',
|
||||||
|
url: 'http://opensourcedesign.org',
|
||||||
|
recipientProfile: 'QmT2A7rY4e7uoKktkcFHQNN7BD1oXdZTgd8wNkr1u9nNVE'
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.ok(tv4.validate(model.contributionToJSON(), schemas['contribution']));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
|
||||||
|
moduleFor('route:proposals/new', 'Unit | Route | proposals/new', {
|
||||||
|
// Specify the other units that are required for this test.
|
||||||
|
// needs: ['controller:foo']
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let route = this.subject();
|
||||||
|
assert.ok(route);
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
|
||||||
|
moduleFor('service:ipfs', 'Unit | Service | ipfs', {
|
||||||
|
// Specify the other units that are required for this test.
|
||||||
|
// needs: ['service:foo']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace this with your real tests.
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let service = this.subject();
|
||||||
|
assert.ok(service);
|
||||||
|
});
|
||||||
Vendored
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user