Compare commits

..

70 Commits

Author SHA1 Message Date
basti 970b390509 Show contribution history from events 2017-08-14 17:33:38 +02:00
basti b8f4040def Get contributions from minted events (WIP) 2017-08-14 17:15:11 +02:00
basti 8fbecd928b Update deps 2017-08-14 17:14:17 +02:00
basti b1db64a07d Merge pull request #23 from 67P/feature/22-handle_locked_metamask
Ask user to unlock their account
2017-06-14 22:15:18 +02:00
basti 22affa7663 Edit account-unlock message/hint 2017-06-14 07:43:38 +02:00
galfert b125d941ba Ask user to unlock their account 2017-06-14 01:55:39 +02:00
basti 9b37f130f3 Merge pull request #21 from 67P/feature/wait_for_web3
Pause app boot until window load event fired
2017-06-13 14:37:19 +02:00
basti 993cd85e4a Improve waiting for window.onload
In case the window load event was fired before getting to the Ember
initializer, it would pause app loading indefinitely. This makes sure
that app loading is only paused, if there's still an event to wait for.
2017-06-13 14:27:09 +02:00
galfert e51586a78c Pause app boot until window load event fired
Refs #17

Allows the user provided Web3 instance to be inserted before we
try to use it.
2017-06-13 13:35:44 +02:00
basti 8aa86fa553 Merge pull request #16 from 67P/feature/ipfs-metadata
Move all metadata to IPFS with proper schemas
2017-06-09 00:28:14 +02:00
basti 510f7de693 Use HTTPS for production IPFS 2017-06-08 23:56:43 +02:00
basti 918ae5b2d1 Fix property dep 2017-06-08 17:06:19 +02:00
basti 6263254a1b Specify radix parameter for parsing amount 2017-06-08 16:53:40 +02:00
basti 99e921c302 Throw error when file couldn't be fetched from IPFS 2017-06-08 16:42:16 +02:00
basti 7f2f781a77 Don't try to save proposal when invalid 2017-06-08 16:41:53 +02:00
basti 3571cc633e Improve computed-property dependency 2017-06-08 16:41:03 +02:00
basti 2011636173 Add contribution details as title 2017-06-07 13:01:35 +02:00
basti cbb15f4d6f Fix mobile layout, add contribution kind to props 2017-06-07 12:46:03 +02:00
basti 33072900e9 Use contributor's display name for proposal list 2017-06-07 12:45:30 +02:00
basti c98a50cd42 Fix mobile layout
Unfortunately also breaks the animation when opening contributor
details for now. Couldn't get it to work again.
2017-06-07 11:11:01 +02:00
basti 1f41dbcae5 Load contribution details from IPFS 2017-06-07 10:35:57 +02:00
basti a918719b8f Decrease overall UI size a bit 2017-06-07 10:35:36 +02:00
basti 7b91e64af4 Create contribution IPFS objects, attach to proposals 2017-06-06 21:27:05 +02:00
basti 0afbeea01b Use newly generated UUID as ID for new contributor 2017-06-06 19:57:24 +02:00
basti 0510cf2389 Set contributor kind from IPFS profile 2017-06-06 19:56:55 +02:00
basti c893f91ce2 Improve logging 2017-06-06 19:56:40 +02:00
basti eced2f423f Minimal validation for amount 2017-06-06 19:56:15 +02:00
basti 6eeb505701 Use correct property for dep 2017-06-06 19:55:59 +02:00
basti 4cb3f5d8ba Use profile data from IPFS for contributor models 2017-06-06 19:05:42 +02:00
basti 372a8a9b2d Unify author value across packages 2017-06-06 13:33:41 +02:00
basti a714601c63 Update kosmos-schemas
This should fix the CI builds.
2017-06-05 22:10:10 +02:00
basti 240d446b97 Remove obsolete output 2017-06-05 22:08:13 +02:00
basti 33e81a7668 Store proper contributor profile in IPFS 2017-06-05 21:58:46 +02:00
bumi f8b75cca61 Manually set networkId
By default kredits-contracts gets the networkId from the web3 object.
This causes some random JSON RPC errors when using MetaMask:
https://github.com/MetaMask/metamask-plugin/issues/1538

This set the default networkId to 17 which is our custom private
network. For development purposes the networkId can also be set
dynamically through the config:networkId local storage entry.
2017-06-04 19:28:14 +02:00
bumi 32e1bbd617 no more evil while loop 2017-06-04 19:27:59 +02:00
basti 1de26004ab Use correct properties as dependency 2017-06-04 17:09:21 +02:00
bumi 0a66c19b0a Specify localhost as web3 provider URL for local deployments 2017-06-01 15:06:52 +02:00
basti 40d6b9905a Observe kredits property in computed array 2017-05-31 17:06:50 +02:00
basti 228825053c Update metadata, version 2017-05-31 16:54:08 +02:00
basti eda0f7a467 Hide new-proposal link for non-wallet users 2017-05-20 21:25:48 +02:00
basti 3052c9dfc5 Merge pull request #15 from 67P/feature/12-new_proposal_contributor_list
Choose contributors from list for new proposals
2017-05-20 18:52:41 +02:00
galfert dbb4094eab Merge pull request #14 from 67P/feature/5-public_ipfs
Use public IPFS node, improve config
2017-05-20 18:14:44 +02:00
galfert f875cd7b91 Choose contributors from list for new proposals 2017-05-20 02:37:11 +02:00
basti 45f17100a2 Use public IPFS node, improve config
Only use local IPFS in development. Set default parity provider URL and
overwrite if set in ENV.
2017-05-16 15:28:11 +02:00
basti dda5a8d350 Merge pull request #11 from 67P/feature/contributors_list
Contributor list improvements
2017-05-14 19:28:17 +02:00
basti 4f57ceaee9 Remove debug log statement 2017-05-14 16:27:43 +02:00
basti 2405e3e118 Show correct number for kredits distribution 2017-05-14 16:25:02 +02:00
basti d7aaadea2b Only show conributors with Kredits in list 2017-05-14 16:16:12 +02:00
basti 8d07c7d741 Update page title 2017-05-14 15:18:15 +02:00
basti 2b260bccd5 Merge pull request #10 from 67P/feature/locally-configurable-provider
Add support to locally conifgure web3 provider
2017-05-14 15:05:31 +02:00
bumi 7299394809 Add support to locally conifgure web3 provider
If no web3 is provided from the browser we check if a
config:web3ProviderUrl item is set in localstorage.
If yes we connect to that provider otherwise use the default config.
Helpful for working simultanously with different chains and providers.
2017-05-14 12:37:47 +02:00
basti 0b7afc0dd5 Update kredits, be expressive about contract options 2017-05-14 01:09:53 +02:00
basti f57e90941c Merge pull request #9 from 67P/configurable-provider
Make web3 provider URL configurable; default to infura
2017-05-13 17:06:33 +02:00
basti be6c5574ff Change default Ethereum RPC provider
Use our own Parity node.
2017-05-13 17:00:04 +02:00
bumi 859cca3820 Merge 2017-05-12 20:04:28 +02:00
bumi ba1cd1cd49 Make web3 provider URL configurable; default to infura
This uses infura (kovan network) as the default web3 provider URL
2017-05-12 17:17:20 +02:00
bumi a1f73b0307 Readme about locally deployed contracts 2017-05-08 13:02:26 +02:00
bumi 3be14b945a Use kredits-contracts from npm
This also configures the npm scripts to work with locally deployed
contracts
2017-05-08 13:00:33 +02:00
bumi 675dff7314 Add support for new splitted Token contract 2017-05-05 20:25:57 +02:00
basti 2ff76c902b Update author info 2017-05-03 17:22:06 +02:00
galfert 642015700e Don't resolve promises after rejecting them 2017-05-03 11:53:21 +02:00
galfert 969ef8d1ed Add proposal form
Closes #6

Form can be prefilled using query params on the route. The params are
`recipient`, `amount`, `url` and `ipfsHash`.
2017-05-03 11:52:42 +02:00
basti b5f8216f7f Show checkmark placeholder 2017-02-19 13:53:19 +08:00
basti e6740d9b4f Funky custom checkmark 2017-02-19 13:49:54 +08:00
basti 2e61b4c8ad Add checkbox for isCore 2017-02-19 11:44:24 +08:00
basti 62a0733a11 Use corrrect (temporary) placeholder 2017-02-19 11:16:51 +08:00
basti 09aa8a5669 Remove obsolete TODO 2017-02-19 11:15:18 +08:00
basti 01c1a9c804 Toggle metadata visibility by clicking name 2017-02-19 11:13:14 +08:00
basti 0a2f44cec8 Add metadata row to contributor list
Contains links to Ethereum address and IPFS content.
2017-02-19 10:30:56 +08:00
basti e7fa188797 Store contributor profile data in IPFS
This stores the GitHub username and UID in IPFS, and stores the
resulting IPFS hash in the contract. Now we can remove the GitHub
profile data from the contract entirely.
2017-02-18 18:44:14 +08:00
41 changed files with 1506 additions and 156 deletions
+24
View File
@@ -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/)
+43 -32
View File
@@ -1,41 +1,58 @@
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,
ipfsHash: null,
isCore: true,
kredits: service(),
newContributor: null,
inProgress: false, inProgress: false,
isValidId: function() {
return Ember.isPresent(this.get('id'));
}.property('id'),
isValidRealName: function() {
return Ember.isPresent(this.get('realName'));
}.property('realName'),
isValidAddress: function() { isValidAddress: function() {
return this.get('kredits.web3Instance').isAddress(this.get('address')); return this.get('kredits.web3')
}.property('address'), .isAddress(this.get('newContributor.address'));
}.property('kredits.web3', 'newContributor.address'),
isValid: function() { isValidName: function() {
return this.get('isValidId') && this.get('isValidRealName') && this.get('isValidAddress'); return isPresent(this.get('newContributor.name'));
}.property('isValidAddress', 'isValidId', 'isValidRealName'), }.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() { reset: function() {
this.setProperties({ this.setProperties({
id: null, newContributor: Contributor.create({ kind: 'person' }),
realName: null,
address: null,
ipfsHash: null,
isCore: true,
inProgress: false inProgress: false
}); });
}, },
actions: { actions: {
@@ -49,13 +66,7 @@ export default Ember.Component.extend({
if (this.get('isValid')) { if (this.get('isValid')) {
this.set('inProgress', true); this.set('inProgress', true);
this.get('kredits').addContributor( this.get('kredits').addContributor(this.get('newContributor')).then(contributor => {
this.get('address'),
this.get('realName'),
'', // TODO: this.get('ipfsHash')
this.get('isCore'),
this.get('id')
).then(contributor => {
this.reset(); this.reset();
this.get('contributors').pushObject(contributor); this.get('contributors').pushObject(contributor);
window.scroll(0,0); window.scroll(0,0);
+46 -13
View File
@@ -1,25 +1,58 @@
<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=(if isValidId 'valid' '')}}
</p>
<p>
{{input name="realname"
type="text"
placeholder="Carl Sagan"
value=realName
class=(if isValidRealName 'valid' '')}}
</p> </p>
<p> <p>
{{input name="address" {{input name="address"
type="text" type="text"
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
value=address value=newContributor.address
class=(if isValidAddress 'valid' '')}} class=(if isValidAddress 'valid' '')}}
</p> </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"> <p class="actions">
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}} {{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
</p> </p>
+66
View File
@@ -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);
});
}
}
});
+42
View File
@@ -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}}
+15 -1
View File
@@ -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);
}
}
}
}); });
+12 -2
View File
@@ -1,14 +1,24 @@
<tbody> <tbody>
{{#each contributors as |contributor|}} {{#each contributors as |contributor|}}
<tr class="{{if contributor.isCurrentUser 'current-user'}}"> <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>
+3 -4
View File
@@ -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}}
+9 -3
View File
@@ -23,7 +23,7 @@ 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.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).github_username); p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
return p; return p;
}); });
return proposals; return proposals;
@@ -33,7 +33,7 @@ export default Ember.Controller.extend({
let proposals = this.get('model.proposals') let proposals = this.get('model.proposals')
.filterBy('executed', true) .filterBy('executed', true)
.map(p => { .map(p => {
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).github_username); p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
return p; return p;
}); });
return proposals; return proposals;
@@ -43,8 +43,14 @@ export default Ember.Controller.extend({
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() { watchContractEvents: function() {
let events = this.get('kredits.kreditsContract') let events = this.get('kredits.kreditsContract')
+29
View File
@@ -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
View File
@@ -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>Kredits</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"}}
+20
View File
@@ -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();
});
}
};
+53
View File
@@ -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;
},
});
+104 -1
View File
@@ -1,16 +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, 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, ipfsHash: null,
kredits: null, kredits: null,
isCore: false,
isCurrentUser: 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());
}
}); });
+87 -1
View File
@@ -1,16 +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,
creatorAddress: null, creatorAddress: null,
recipientAddress: null, recipientAddress: null,
recipientName: 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());
}
}); });
+3
View File
@@ -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
View File
@@ -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' })
}); });
} }
+37
View File
@@ -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);
}
});
+4
View File
@@ -18,6 +18,7 @@ export default Ember.Service.extend({
storeFile(content) { storeFile(content) {
let ipfs = this.get('ipfs'); let ipfs = this.get('ipfs');
return ipfs.add(new ipfs.Buffer(content)).then(res => { 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; return res[0].hash;
}); });
}, },
@@ -25,6 +26,9 @@ export default Ember.Service.extend({
getFile(hash) { getFile(hash) {
return this.get('ipfs').cat(hash, { buffer: true }).then(res => { return this.get('ipfs').cat(hash, { buffer: true }).then(res => {
return res.toString(); return res.toString();
}, err => {
Ember.Logger.error('[ipfs] error trying to fetch file', hash, err);
throw err;
}); });
} }
+160 -40
View File
@@ -3,11 +3,21 @@ 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 kreditsContracts from 'npm:kredits-contracts';
import uuid from 'npm:uuid';
export default Ember.Service.extend({ const {
Service,
isPresent,
inject: {
service
}
} = Ember;
ipfs: Ember.inject.service(), 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.)
@@ -25,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(config.web3ProviderUrl); let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
let provider = new Web3.providers.HttpProvider(providerUrl);
web3Instance = new Web3(provider); web3Instance = new Web3(provider);
} }
@@ -37,24 +48,51 @@ export default Ember.Service.extend({
currentUserAccounts: function() { currentUserAccounts: function() {
return (this.get('web3Provided') && this.get('web3').eth.accounts) || []; return (this.get('web3Provided') && this.get('web3').eth.accounts) || [];
}.property('web3', 'web3Provided'), }.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() { kreditsContract: function() {
if (this.get('kreditsContractInstance')) { if (this.get('kreditsContractInstance')) {
return this.get('kreditsContractInstance'); return this.get('kreditsContractInstance');
} }
console.log(config.ethereumChain); let contract = this.initializeKreditsContract();
let contract = kreditsContracts(this.get('web3'), config.ethereumChain)['Kredits'];
this.set('kreditsContractInstance', contract); this.set('kreditsContractInstance', contract);
// window.Kredits = contract;
return 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) => { 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);
}); });
}); });
@@ -62,19 +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],
ipfsHash: person[3],
kredits: balance.toNumber(), kredits: balance.toNumber(),
isCurrentUser: this.get('currentUserAccounts').includes(address) 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)); }).catch(err => reject(err));
@@ -83,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++) {
@@ -96,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],
@@ -108,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);
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)); }).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++) {
@@ -131,34 +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);
}); });
}); });
}, },
addContributor(address, name, ipfsHash, isCore, id) { addContributor(contributor) {
Ember.Logger.debug('[kredits] add contributor', name, address); 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) => { return new Ember.RSVP.Promise((resolve, reject) => {
this.get('kreditsContract').addContributor(address, name, ipfsHash, isCore, id, (err, data) => { this.get('ipfs').storeFile(contributor.serialize()).then(ipfsHash => {
if (err) { reject(err); } contributor.set('ipfsHash', ipfsHash);
Ember.Logger.debug('[kredits] add contributor response', data); this.get('kreditsContract').addContributor(contributor.address, contributor.name, contributor.ipfsHash, contributor.isCore, id, (err, data) => {
let contributor = Contributor.create({ if (err) { reject(err); return; }
address: address, Ember.Logger.debug('[kredits] add contributor response', data);
github_username: name, resolve(contributor);
github_uid: id,
ipfsHash: ipfsHash,
kredits: 0,
isCurrentUser: this.get('currentUserAccounts').includes(address)
}); });
resolve(contributor);
}); });
}); });
}, },
logKreditsContract: function() { addProposal(proposal) {
Ember.Logger.debug('[kredits] kreditsContract', this.get('kreditsContract')); return new Ember.RSVP.Promise((resolve, reject) => {
}.on('init') 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')
}); });
+8
View File
@@ -0,0 +1,8 @@
$blue: #68d7fb;
$purple: #8f68fb;
$pink: #e068fb;
$green: #97fb68;
$yellow: #fbe468;
$red: #fb6868;
$primaryColor: $blue;
+22 -4
View File
@@ -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";
+60 -2
View File
@@ -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,6 +26,31 @@ 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] {
@@ -31,6 +60,35 @@ section#add-contributor {
} }
} }
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;
}
}
}
+43 -7
View File
@@ -7,12 +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;
@@ -36,11 +78,5 @@ table.contributor-list {
} }
} }
} }
&.current-user {
td {
background-color: rgba(255,255,255,0.2);
}
}
} }
} }
+2 -2
View File
@@ -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;
+9 -4
View File
@@ -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 {
+13 -7
View File
@@ -8,7 +8,7 @@
<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>
@@ -22,17 +22,23 @@
{{proposal-list proposals=proposalsOpenSorted {{proposal-list proposals=proposalsOpenSorted
confirmAction="confirmProposal" confirmAction="confirmProposal"
contractInteractionEnabled=contractInteractionEnabled}} 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 {{contribution-list contributions=model.contributions
confirmAction="confirmProposal"}} contributors=model.contributors}}
</div> </div>
</section> </section>
@@ -42,8 +48,8 @@
</header> </header>
<div class="content"> <div class="content">
{{add-contributor kredits=kredits {{add-contributor contributors=model.contributors
contributors=model.contributors newContributor=model.newContributor
contractInteractionEnabled=contractInteractionEnabled}} contractInteractionEnabled=contractInteractionEnabled}}
</div> </div>
</section> </section>
+10
View File
@@ -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>
+29 -8
View File
@@ -1,10 +1,13 @@
/* jshint node: true */ /* jshint node: true */
const fs = require('fs');
module.exports = function(environment) { module.exports = function(environment) {
var ENV = { var ENV = {
modulePrefix: 'kredits-web', modulePrefix: 'kredits-web',
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
@@ -22,6 +25,7 @@ module.exports = function(environment) {
}, },
browserify: { browserify: {
tests: true,
transform: [ transform: [
["babelify", { ["babelify", {
presets: ["es2015"], presets: ["es2015"],
@@ -30,22 +34,41 @@ module.exports = function(environment) {
] ]
}, },
web3ProviderUrl: "https://parity.kosmos.org:8545", contractMetadata: {},
ethereumChain: "testnet",
web3ProviderUrl: 'https://parity.kosmos.org:8545',
ipfs: { ipfs: {
host: 'localhost', host: 'ipfs.kosmos.org',
port: '5001', port: '5444',
protocol: 'http' 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.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.ethereumChain = 'dev'; 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') {
@@ -60,8 +83,6 @@ module.exports = function(environment) {
} }
if (environment === 'production') { if (environment === 'production') {
ENV.ethereumChain = 'testnet';
ENV.web3ProviderUrl = 'https://ropsten.infura.io';
} }
return ENV; return ENV;
+291
View File
@@ -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"
}
}
]
+23 -7
View File
@@ -1,22 +1,33 @@
{ {
"name": "kredits-web", "name": "kredits-web",
"version": "0.1.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": {
"start": "ember server", "start": "ember server",
"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", "test": "ember test",
"postinstall": "bower install", "postinstall": "bower install",
"build": "ember build", "build": "ember build",
"build-prod": "ember build --environment production", "build-prod": "ember build --environment production",
"update-version-file": "bash scripts/update-version-file.sh", "update-version-file": "bash scripts/update-version-file.sh",
"deploy": "npm run build-prod && npm run update-version-file && bash scripts/deploy.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", "babel-preset-es2015": "^6.22.0",
@@ -34,16 +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", "ipfs-api": "^12.1.7",
"kredits-contracts": "67P/kredits-contracts", "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": {
@@ -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');
});
+37 -10
View File
@@ -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);
});
+14
View File
@@ -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);
});
+18 -1
View File
@@ -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']));
});
+43 -3
View File
@@ -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']));
}); });
+11
View File
@@ -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);
});