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.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* [ember.js](http://emberjs.com/)
|
||||
|
||||
@@ -1,33 +1,79 @@
|
||||
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,
|
||||
realName: null,
|
||||
address: null,
|
||||
export default Component.extend({
|
||||
|
||||
classId: function() {
|
||||
let value = this.get('id');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('id'),
|
||||
kredits: service(),
|
||||
|
||||
classRealName: function() {
|
||||
let value = this.get('realName');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('realName'),
|
||||
newContributor: null,
|
||||
inProgress: false,
|
||||
|
||||
classAddress: function() {
|
||||
let value = this.get('address');
|
||||
return (Ember.isEmpty(value)) ? null : 'valid';
|
||||
}.property('address'),
|
||||
isValidAddress: function() {
|
||||
return this.get('kredits.web3')
|
||||
.isAddress(this.get('newContributor.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: {
|
||||
|
||||
save() {
|
||||
console.log('id', this.get('id'));
|
||||
console.log('realName', this.get('realName'));
|
||||
console.log('address', this.get('address'));
|
||||
if (!this.get('contractInteractionEnabled')) {
|
||||
alert('Only core team members can add new contributors. Please ask someone to set you up.');
|
||||
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"}}>
|
||||
<p>
|
||||
{{input name="id"
|
||||
type="text"
|
||||
placeholder="GitHub UID (123)"
|
||||
value=id
|
||||
class=classId}}
|
||||
</p>
|
||||
<p>
|
||||
{{input name="realname"
|
||||
type="text"
|
||||
placeholder="Carl Sagan"
|
||||
value=realName
|
||||
class=classRealName}}
|
||||
{{input type="checkbox" name="is-core" id="is-core" checked=newContributor.isCore}}
|
||||
<label for="is-core" class="checkbox">
|
||||
Core team member (can add contributors)
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
{{input name="address"
|
||||
type="text"
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
value=address
|
||||
class=classAddress}}
|
||||
value=newContributor.address
|
||||
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 class="actions">
|
||||
<input type="submit" value="Save">
|
||||
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
|
||||
</p>
|
||||
</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({
|
||||
|
||||
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>
|
||||
{{#each contributors as |contributor|}}
|
||||
<tr>
|
||||
<tr class="{{if contributor.isCurrentUser 'current-user'}}" {{action "toggleContributorInfo" contributor}}>
|
||||
<td class="person">
|
||||
<img class="avatar" src={{contributor.avatarURL}}>
|
||||
{{contributor.github_username}}
|
||||
{{contributor.name}}
|
||||
</td>
|
||||
<td class="kredits">
|
||||
<span class="amount">{{contributor.kredits}}</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</td>
|
||||
</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}}
|
||||
</tbody>
|
||||
|
||||
@@ -8,7 +8,11 @@ export default Ember.Component.extend({
|
||||
actions: {
|
||||
|
||||
confirm(proposalId) {
|
||||
this.sendAction('confirmAction', proposalId);
|
||||
if (this.get('contractInteractionEnabled')) {
|
||||
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|}}
|
||||
<li>
|
||||
<span class="id">#{{proposal.id}}</span>
|
||||
Issue{{#if proposal.executed}}d{{/if}}
|
||||
<li data-proposal-id={{proposal.id}} title="({{proposal.kind}}) {{proposal.description}}">
|
||||
<span class="category {{proposal.kind}}">♥</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>
|
||||
|
||||
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Ember from 'ember';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
|
||||
const {
|
||||
computed,
|
||||
@@ -22,28 +23,117 @@ export default Ember.Controller.extend({
|
||||
let proposals = this.get('model.proposals')
|
||||
.filterBy('executed', false)
|
||||
.map(p => {
|
||||
p.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
||||
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', 'model.contributors.[]'),
|
||||
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||
|
||||
proposalsClosed: function() {
|
||||
let proposals = this.get('model.proposals')
|
||||
.filterBy('executed', true)
|
||||
.map(p => {
|
||||
p.recipientName = this.findContributorByAddress(p.recipientAddress).github_username;
|
||||
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
|
||||
return p;
|
||||
});
|
||||
return proposals;
|
||||
}.property('model.proposals.[]', 'model.contributors.[]'),
|
||||
}.property('model.proposals.[]', 'model.proposals.@each.executed', 'model.contributors.[]'),
|
||||
|
||||
proposalsSorting: ['id:desc'],
|
||||
proposalsClosedSorted: Ember.computed.sort('proposalsClosed', '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'],
|
||||
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: {
|
||||
|
||||
|
||||
@@ -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>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>KreditsWeb</title>
|
||||
<title>Kosmos Kredits</title>
|
||||
<meta name="description" content="">
|
||||
<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">
|
||||
|
||||
{{content-for "head-footer"}}
|
||||
|
||||
<script>
|
||||
window.addEventListener('load', function() {
|
||||
window.windowLoadComplete = true;
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<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';
|
||||
|
||||
const {
|
||||
isPresent,
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Object.extend({
|
||||
|
||||
address: null,
|
||||
name: null,
|
||||
kind: null,
|
||||
url: null,
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
wiki_username: null,
|
||||
ipfsHash: null,
|
||||
kredits: null,
|
||||
address: null,
|
||||
isCore: false,
|
||||
isCurrentUser: false,
|
||||
|
||||
avatarURL: function() {
|
||||
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';
|
||||
|
||||
const {
|
||||
isPresent,
|
||||
isEmpty,
|
||||
} = Ember;
|
||||
|
||||
export default Ember.Object.extend({
|
||||
|
||||
id: null,
|
||||
creator: null,
|
||||
recipient: null,
|
||||
creatorAddress: null,
|
||||
recipientAddress: null,
|
||||
recipientName: null,
|
||||
recipientProfile: null,
|
||||
votesCount: null,
|
||||
votesNeeded: null,
|
||||
amount: null,
|
||||
executed: null,
|
||||
contribution: null,
|
||||
kind: null,
|
||||
description: 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() {
|
||||
this.route('spinner');
|
||||
this.route('proposals', function() {
|
||||
this.route('new');
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
||||
+19
-3
@@ -1,17 +1,33 @@
|
||||
import Ember from 'ember';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
|
||||
export default Ember.Route.extend({
|
||||
|
||||
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() {
|
||||
let kredits = this.get('kredits');
|
||||
|
||||
return Ember.RSVP.hash({
|
||||
contributors: kredits.getContributors(),
|
||||
totalSupply: kredits.getValueFromContract('totalSupply'),
|
||||
contributorsCount: kredits.getValueFromContract('contributorsCount'),
|
||||
proposals: kredits.getProposals()
|
||||
totalSupply: kredits.getValueFromContract('tokenContract', 'totalSupply'),
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
+172
-26
@@ -3,9 +3,21 @@ import Web3 from 'npm:web3';
|
||||
import config from 'kredits-web/config/environment';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
import Proposal from 'kredits-web/models/proposal';
|
||||
import Contribution from 'kredits-web/models/contribution';
|
||||
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,
|
||||
web3Provided: false, // Web3 provided (using Mist Browser, Metamask et al.)
|
||||
@@ -23,7 +35,8 @@ export default Ember.Service.extend({
|
||||
this.set('web3Provided', true);
|
||||
} else {
|
||||
Ember.Logger.debug('[kredits] Creating new instance from npm module class');
|
||||
let provider = new Web3.providers.HttpProvider(config.web3ProviderUrl);
|
||||
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
|
||||
let provider = new Web3.providers.HttpProvider(providerUrl);
|
||||
web3Instance = new Web3(provider);
|
||||
}
|
||||
|
||||
@@ -33,22 +46,53 @@ export default Ember.Service.extend({
|
||||
return web3Instance;
|
||||
}.property('web3Instance'),
|
||||
|
||||
currentUserAccounts: function() {
|
||||
return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
|
||||
}.property('web3Provided', 'web3'),
|
||||
|
||||
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 = kreditsContracts(this.get('web3'))['Kredits'];
|
||||
let contract = this.initializeKreditsContract();
|
||||
|
||||
window.Kredits = contract;
|
||||
this.set('kreditsContractInstance', contract);
|
||||
// window.Kredits = contract;
|
||||
return contract;
|
||||
}.property('web3'),
|
||||
}.property('kreditsContractInstance', 'web3'),
|
||||
|
||||
getValueFromContract(contractMethod, ...args) {
|
||||
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) => {
|
||||
this.get('kreditsContract')[contractMethod](...args, (err, data) => {
|
||||
if (err) { reject(err); }
|
||||
this.get(contract)[contractMethod](...args, (err, data) => {
|
||||
if (err) { reject(err); return; }
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
@@ -56,18 +100,22 @@ export default Ember.Service.extend({
|
||||
|
||||
getContributorData(i) {
|
||||
let promise = new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.getValueFromContract('contributorAddresses', i).then(address => {
|
||||
this.getValueFromContract('contributors', address).then(person => {
|
||||
this.getValueFromContract('balanceOf', address).then(balance => {
|
||||
this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => {
|
||||
this.getValueFromContract('kreditsContract', 'contributors', address).then(person => {
|
||||
this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => {
|
||||
Ember.Logger.debug('[kredits] person', address, person);
|
||||
|
||||
let contributor = Contributor.create({
|
||||
address: address,
|
||||
github_username: person[1],
|
||||
github_uid: person[0],
|
||||
ipfsHash: person[3],
|
||||
kredits: balance.toNumber()
|
||||
ipfsHash: person[2],
|
||||
kredits: balance.toNumber(),
|
||||
isCurrentUser: this.get('currentUserAccounts').includes(address)
|
||||
});
|
||||
Ember.Logger.debug('[kredits] contributor', contributor);
|
||||
resolve(contributor);
|
||||
|
||||
contributor.loadProfile(this.get('ipfs')).then(
|
||||
() => resolve(contributor),
|
||||
err => reject(err)
|
||||
);
|
||||
});
|
||||
});
|
||||
}).catch(err => reject(err));
|
||||
@@ -76,7 +124,7 @@ export default Ember.Service.extend({
|
||||
},
|
||||
|
||||
getContributors() {
|
||||
return this.getValueFromContract('contributorsCount').then(contributorsCount => {
|
||||
return this.getValueFromContract('kreditsContract', 'contributorsCount').then(contributorsCount => {
|
||||
let contributors = [];
|
||||
|
||||
for(var i = 0; i < contributorsCount.toNumber(); i++) {
|
||||
@@ -89,7 +137,7 @@ export default Ember.Service.extend({
|
||||
|
||||
getProposalData(i) {
|
||||
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({
|
||||
id : i,
|
||||
creatorAddress : p[0],
|
||||
@@ -101,15 +149,23 @@ export default Ember.Service.extend({
|
||||
url : p[6],
|
||||
ipfsHash : p[7]
|
||||
});
|
||||
Ember.Logger.debug('[kredits] proposal', proposal);
|
||||
resolve(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);
|
||||
}
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
getProposals() {
|
||||
return this.getValueFromContract('proposalsCount').then(proposalsCount => {
|
||||
return this.getValueFromContract('kreditsContract', 'proposalsCount').then(proposalsCount => {
|
||||
let proposals = [];
|
||||
|
||||
for(var i = 0; i < proposalsCount.toNumber(); i++) {
|
||||
@@ -124,15 +180,105 @@ export default Ember.Service.extend({
|
||||
Ember.Logger.debug('[kredits] vote for', proposalId);
|
||||
return new Ember.RSVP.Promise((resolve, reject) => {
|
||||
this.get('kreditsContract').vote(proposalId, (err, data) => {
|
||||
if (err) { reject(err); }
|
||||
if (err) { reject(err); return; }
|
||||
Ember.Logger.debug('[kredits] vote response', data);
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
logKreditsContract: function() {
|
||||
Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract'));
|
||||
}.on('init')
|
||||
addContributor(contributor) {
|
||||
Ember.Logger.debug('[kredits] add contributor', contributor);
|
||||
|
||||
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 "settings/breakpoints";
|
||||
@import "layout";
|
||||
@import "colors";
|
||||
|
||||
$font-family-sans: 'Open Sans', sans-serif;
|
||||
|
||||
@@ -23,7 +24,7 @@ body {
|
||||
background-repeat: none;
|
||||
background-attachment: fixed;
|
||||
font-family: $font-family-sans;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -43,7 +44,7 @@ h1, h2, h3, h4, h5, input, button {
|
||||
section {
|
||||
h2 {
|
||||
font-size: 2.8rem;
|
||||
color: lightblue;
|
||||
color: $primaryColor;
|
||||
@include media($mobile) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
@@ -54,7 +55,7 @@ section {
|
||||
p.stats {
|
||||
padding-top: 3rem;
|
||||
font-size: 1rem;
|
||||
color: lightblue;
|
||||
color: white;
|
||||
text-align: center;
|
||||
span.number {
|
||||
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] {
|
||||
display: inline-block;
|
||||
border: 1px solid rgba(22, 21, 40, 1);
|
||||
background-color: rgba(22, 21, 40, 0.6);
|
||||
color: lightblue;
|
||||
color: $primaryColor;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
@@ -87,3 +104,4 @@ button, input[type=submit] {
|
||||
@import "components/contributor-list";
|
||||
@import "components/add-contributor";
|
||||
@import "components/proposal-list";
|
||||
@import "components/contribution-list";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
section#add-contributor {
|
||||
section#add-contributor, section#add-proposal {
|
||||
|
||||
form {
|
||||
|
||||
@@ -8,10 +8,14 @@ section#add-contributor {
|
||||
&.actions {
|
||||
padding-top: 1rem;
|
||||
text-align: center;
|
||||
a {
|
||||
color: $primaryColor;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
input[type=text], select {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
@@ -22,10 +26,67 @@ section#add-contributor {
|
||||
&:focus, &.valid {
|
||||
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] {
|
||||
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,32 +7,75 @@ table.contributor-list {
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
&.person {
|
||||
text-align: left;
|
||||
font-size: 1.4rem;
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.kredits {
|
||||
text-align: right;
|
||||
.amount {
|
||||
|
||||
td {
|
||||
padding: 0 1.2rem;
|
||||
line-height: 4.2rem;
|
||||
|
||||
&.person {
|
||||
text-align: left;
|
||||
font-size: 1.4rem;
|
||||
img.avatar {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.2rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
.symbol {
|
||||
font-size: 1rem;
|
||||
padding-left: 0.2rem;
|
||||
|
||||
&.kredits {
|
||||
text-align: right;
|
||||
.amount {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.symbol {
|
||||
font-size: 1rem;
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
margin-top: 12rem;
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
color: lightblue;
|
||||
color: $primaryColor;
|
||||
|
||||
@include media($mobile) {
|
||||
margin-top: 6rem;
|
||||
@@ -23,7 +23,7 @@
|
||||
margin-bottom: 2rem;
|
||||
|
||||
#path-comet {
|
||||
fill: lightblue;
|
||||
fill: $primaryColor;
|
||||
opacity: 0.1;
|
||||
|
||||
animation-name: pulse;
|
||||
|
||||
@@ -14,9 +14,14 @@ ul.proposal-list {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.id {
|
||||
color: lightblue;
|
||||
.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;
|
||||
@@ -31,8 +36,8 @@ ul.proposal-list {
|
||||
}
|
||||
.votes {
|
||||
font-size: 1rem;
|
||||
color: lightblue;
|
||||
padding-left: 1rem;
|
||||
color: $primaryColor;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
+34
-25
@@ -8,39 +8,48 @@
|
||||
|
||||
<p class="stats">
|
||||
<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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if contractInteractionEnabled}}
|
||||
{{#if proposalsOpen}}
|
||||
<section id="proposals-open">
|
||||
<header>
|
||||
<h2>Open Proposals</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{proposal-list proposals=proposalsOpenSorted confirmAction="confirmProposal"}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section id="proposals-closed">
|
||||
{{#if proposalsOpen}}
|
||||
<section id="proposals-open">
|
||||
<header>
|
||||
<h2>Closed Proposals</h2>
|
||||
<h2>Open Proposals</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{proposal-list proposals=proposalsClosedSorted confirmAction="confirmProposal"}}
|
||||
</div>
|
||||
</section>
|
||||
{{proposal-list proposals=proposalsOpenSorted
|
||||
confirmAction="confirmProposal"
|
||||
contractInteractionEnabled=contractInteractionEnabled}}
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Add Contributor</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor kredits=kredits}}
|
||||
{{#if contractInteractionEnabled}}
|
||||
<p class="actions">
|
||||
{{#link-to 'proposals.new'}}Create new proposal{{/link-to}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<section id="contributions">
|
||||
<header>
|
||||
<h2>Contributions</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contribution-list contributions=model.contributions
|
||||
contributors=model.contributors}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="add-contributor">
|
||||
<header>
|
||||
<h2>Add Contributor</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor contributors=model.contributors
|
||||
newContributor=model.newContributor
|
||||
contractInteractionEnabled=contractInteractionEnabled}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+41
-1
@@ -1,10 +1,13 @@
|
||||
/* jshint node: true */
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = function(environment) {
|
||||
var ENV = {
|
||||
modulePrefix: 'kredits-web',
|
||||
environment: environment,
|
||||
rootURL: '/',
|
||||
locationType: 'auto',
|
||||
|
||||
EmberENV: {
|
||||
FEATURES: {
|
||||
// Here you can enable experimental features on an ember canary build
|
||||
@@ -19,16 +22,53 @@ module.exports = function(environment) {
|
||||
APP: {
|
||||
// Here you can pass flags/options to your application instance
|
||||
// when it is created
|
||||
},
|
||||
|
||||
browserify: {
|
||||
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') {
|
||||
ENV.web3ProviderUrl = "http://139.59.248.169:8545";
|
||||
// ENV.APP.LOG_RESOLVER = true;
|
||||
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||
// ENV.APP.LOG_TRANSITIONS = true;
|
||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = 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') {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
+32
-9
@@ -1,20 +1,37 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"version": "0.0.0",
|
||||
"description": "Small description for kredits-web goes here",
|
||||
"version": "1.0.0",
|
||||
"description": "Contribution dashboard of the Kosmos project",
|
||||
"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": {
|
||||
"doc": "doc",
|
||||
"test": "tests"
|
||||
},
|
||||
"repository": "",
|
||||
"repository": "https://github.com/67P/kredits-web",
|
||||
"scripts": {
|
||||
"build": "ember build",
|
||||
"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": {
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babelify": "^7.3.0",
|
||||
"broccoli-asset-rev": "^2.4.5",
|
||||
"ember-ajax": "^2.4.1",
|
||||
"ember-browserify": "^1.1.13",
|
||||
@@ -28,16 +45,22 @@
|
||||
"ember-cli-jshint": "^2.0.1",
|
||||
"ember-cli-qunit": "^3.0.1",
|
||||
"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-test-loader": "^1.1.0",
|
||||
"ember-cli-uglify": "^1.2.0",
|
||||
"ember-export-application-global": "^1.0.5",
|
||||
"ember-load-initializers": "^0.5.1",
|
||||
"ember-parachute": "0.1.0",
|
||||
"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",
|
||||
"web3": "^0.18.2",
|
||||
"kredits-contracts": "67P/kredits-contracts"
|
||||
"tv4": "^1.3.0",
|
||||
"uuid": "^3.0.1",
|
||||
"web3": "^0.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
|
||||
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 hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
moduleForComponent('add-contributor', 'Integration | Component | add contributor', {
|
||||
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-contributor}}`);
|
||||
|
||||
assert.equal(this.$().text().trim(), '');
|
||||
});
|
||||
// import { moduleForComponent, test } from 'ember-qunit';
|
||||
// import hbs from 'htmlbars-inline-precompile';
|
||||
//
|
||||
// moduleForComponent('add-contributor', 'Integration | Component | add contributor', {
|
||||
// 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-contributor}}`);
|
||||
//
|
||||
// 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';
|
||||
//
|
||||
// moduleFor('controller:index', 'Unit | Controller | index');
|
||||
import Ember from 'ember';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
import { moduleFor, test } from 'ember-qunit';
|
||||
|
||||
// let addFixtures = function(controller) {
|
||||
// controller.set('model', { contributors: [
|
||||
// { github_username: "neo", github_uid: "318", kredits: 10000 },
|
||||
// { github_username: "morpheus", github_uid: "843", kredits: 15000 },
|
||||
// { github_username: "mouse", github_uid: "842", kredits: 5000 },
|
||||
// ]});
|
||||
// };
|
||||
const {
|
||||
isPresent,
|
||||
isEmpty
|
||||
} = Ember;
|
||||
|
||||
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 schemas from 'npm:kosmos-schemas';
|
||||
import tv4 from 'npm:tv4';
|
||||
|
||||
moduleFor('model:contributor', 'Unit | Model | contributor');
|
||||
|
||||
test('avatarURL returns correct URL', function(assert) {
|
||||
test('#avatarURL() returns correct URL', function(assert) {
|
||||
let model = this.subject();
|
||||
model.set('github_uid', '318');
|
||||
|
||||
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 schemas from 'npm:kosmos-schemas';
|
||||
import tv4 from 'npm:tv4';
|
||||
|
||||
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 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);
|
||||
});
|
||||
Reference in New Issue
Block a user