Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26dc78f553 | |||
| 5a618f7ef9 | |||
| 5d29bd9604 | |||
| 97cc71024f | |||
| 58f1382df1 | |||
| 6c5406c0b9 | |||
| b2457421be | |||
| 134eb7d355 | |||
| 1dc1a9767e | |||
| 243a41db88 | |||
| 523d4524b5 | |||
| c21613f20a | |||
| 12b127c75d | |||
| a7dd058d21 | |||
| de6606b29d | |||
| ec701b3130 | |||
| 8e7430ebb9 | |||
| 1deecafee7 | |||
| 51d8b6c8c1 | |||
| b28f6e6bd7 | |||
| 4ab28016a3 | |||
| 6995b67a22 | |||
| 0076d59a42 | |||
| 36b92d97ca | |||
| f272bcfcbc | |||
| 985e1673ac | |||
| fe4af46852 | |||
| 7b9659e22f | |||
| 8be18fc27c | |||
| 7e518780c5 | |||
| 89391c1543 | |||
| 05e3118a0c | |||
| 494b5d9bdd | |||
| f8f1ad644c | |||
| 5a5051bac6 | |||
| 304c0ac8d0 | |||
| cae13ed662 | |||
| 729110f8d1 | |||
| 11f66de6e6 | |||
| 9bf2ae2d7f | |||
| 2a384f3eed | |||
| 6b3666def9 | |||
| 74c8e32cc6 | |||
| 5dde978499 | |||
| 0625ee9d79 | |||
| dc5dfe19f2 | |||
| c518b7f565 | |||
| d13242b210 | |||
| ada029586a | |||
| 7bf5fa444a | |||
| d08d5f9e8d | |||
| a2bd3f76b8 | |||
| a4ca37d882 | |||
| 143317d909 | |||
| d8bcc8ff67 | |||
| e4a39ec67c | |||
| 5b47ba9e11 | |||
| d8f565cd9e | |||
| a9480a09f1 | |||
| 60ba019139 | |||
| 413bcddb1e | |||
| c2273d1778 | |||
| 780afc9bc6 | |||
| a8ae2b0156 | |||
| d6d2b1a61c | |||
| 4c70c0d233 | |||
| 725d36bed6 | |||
| 0a71d7ad91 | |||
| 6a176b5904 | |||
| c7d046aa46 | |||
| aa9c3648c0 | |||
| 9c18593529 | |||
| 52fe7ff026 | |||
| b02967c5ef | |||
| 7236b2d2b3 | |||
| fd1ae0a612 | |||
| 612b76300c | |||
| 42fe01b349 | |||
| 80af572742 | |||
| d7a0817ac6 |
+10
-2
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2017,
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module'
|
||||
},
|
||||
globals: {
|
||||
@@ -41,7 +41,15 @@ module.exports = {
|
||||
env: {
|
||||
browser: false,
|
||||
node: true
|
||||
}
|
||||
},
|
||||
plugins: ['node'],
|
||||
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
|
||||
// add your custom rules and overrides for node files here
|
||||
|
||||
// this can be removed once the following is fixed
|
||||
// https://github.com/mysticatea/eslint-plugin-node/issues/77
|
||||
'node/no-unpublished-require': 'off'
|
||||
})
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ module.exports = {
|
||||
extends: 'recommended',
|
||||
|
||||
rules: {
|
||||
'simple-unless': false
|
||||
'simple-unless': false,
|
||||
'no-nested-interactive': false
|
||||
}
|
||||
};
|
||||
|
||||
@@ -23,9 +23,6 @@ env:
|
||||
# See https://git.io/vdao3 for details.
|
||||
- JOBS=1
|
||||
|
||||
before_install:
|
||||
- npm config set spin false
|
||||
|
||||
script:
|
||||
- npm run lint:hbs
|
||||
- npm run lint:js
|
||||
|
||||
@@ -71,20 +71,20 @@ These are the basic steps to get up and running:
|
||||
|
||||
#### 1. IPFS
|
||||
|
||||
Run a local IPFS deamon in offline mode.
|
||||
Run a local IPFS deamon.
|
||||
|
||||
* Make sure CORS headers are configured. See [IPFS](#ipfs) for more info.
|
||||
* `ipfs daemon --offline`
|
||||
* `ipfs daemon`
|
||||
|
||||
#### 2. kredits-contracts
|
||||
|
||||
Get your local Ethereum development node running. (See [kredits-contracts README](https://github.com/67P/kredits-contracts)
|
||||
Get your local Ethereum development node running. (See [kredits-contracts README](https://github.com/67P/kredits-contracts)
|
||||
for details.
|
||||
|
||||
* Clone [kredits-contracts](https://github.com/67P/kredits-contracts)
|
||||
* `npm install`
|
||||
* `npm run devchain` - runs a local development chain
|
||||
* `npm run bootstrap` - bootstrap runs deploys all the contracts
|
||||
* `npm run bootstrap` - bootstrap runs deploys all the contracts
|
||||
* `npm link` - make the `kredits-contracts` module linkable as `kredits-contracts` on your machine (currently broken :( )
|
||||
|
||||
#### 3. kredits-web
|
||||
@@ -94,10 +94,15 @@ With IPFS and Ethereum/ganache running, you can now start this Ember app.
|
||||
* `npm link kredits-contracts` - link the local `kredits-contracts` package
|
||||
* `npm run start:local` - WEB3_PROVIDER_URL=http://localhost:7545 must be set for local settings
|
||||
|
||||
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO Kernel address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_KERNEL_ADDRESS` environment variable.
|
||||
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_DAO_ADDRESS` environment variable.
|
||||
|
||||
* `KREDITS_KERNEL_ADDRESS=<0xYOURADDRESS> npm run start:local` - find your address with the `npm run dao:address` command in the kredits-contracts repo.
|
||||
* `npm run dao:address` in the kredits-contracts repo to find your address
|
||||
* `KREDITS_DAO_ADDRESS=<0xYOURADDRESS> npm run start:local`
|
||||
|
||||
#### 4. Metamask network
|
||||
|
||||
Switch the network in Metamask to "Custom RPC" with the RPC URL `http://localhost:7545`.
|
||||
|
||||
#### IPFS
|
||||
|
||||
@@ -107,6 +112,7 @@ Install IPFS with your favorite package manager and run
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["localhost:4200"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
|
||||
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8080
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
import moment from 'moment';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
@@ -21,17 +23,20 @@ export default Component.extend({
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('defaultDate', new Date());
|
||||
this.set('defaultDate', moment().startOf('hour').toDate());
|
||||
|
||||
// Default attributes used by reset
|
||||
this.set('attributes', {
|
||||
contributorId: null,
|
||||
kind: null,
|
||||
date: [new Date()],
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
});
|
||||
if (isEmpty(this.attributes)) {
|
||||
this.set('attributes', {
|
||||
contributorId: null,
|
||||
kind: null,
|
||||
date: this.defaultDate,
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
details: null
|
||||
});
|
||||
}
|
||||
|
||||
this.reset();
|
||||
},
|
||||
@@ -49,8 +54,12 @@ export default Component.extend({
|
||||
}
|
||||
|
||||
const attributes = this.getProperties(Object.keys(this.attributes));
|
||||
const [ date/* , time */ ] = attributes.date[0].toISOString().split('T');
|
||||
attributes.date = date;
|
||||
|
||||
let dateInput = (attributes.date instanceof Array) ?
|
||||
attributes.date[0] : attributes.date;
|
||||
|
||||
const [ date, time ] = dateInput.toISOString().split('T');
|
||||
[ attributes.date, attributes.time ] = [ date, time ];
|
||||
|
||||
this.set('inProgress', true);
|
||||
|
||||
@@ -62,7 +71,6 @@ export default Component.extend({
|
||||
window.alert('Something went wrong. Check the browser console for details.');
|
||||
})
|
||||
.finally(() => this.set('inProgress', false));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,54 +1,79 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<p>
|
||||
<select required onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<option value="" selected disabled hidden>Contributor</option>
|
||||
{{#each contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut kind) value="target.value"}}>
|
||||
<option value="" selected disabled hidden>Kind</option>
|
||||
<option value="community" selected={{eq kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
|
||||
<option value="special" selected={{eq kind "special"}}>Special</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
{{ember-flatpickr
|
||||
allowInput=false
|
||||
altFormat="F j, Y"
|
||||
altInput=true
|
||||
altInputClass="date-alt"
|
||||
date=date
|
||||
dateFormat="Y-m-d"
|
||||
defaultDate=defaultDate
|
||||
maxDate=defaultDate
|
||||
onChange=(action (mut date))
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="500"
|
||||
value=amount
|
||||
class=(if isValidAmount "valid" "")}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="Description"
|
||||
value=description
|
||||
class=(if isValidDescription "valid" "")}}
|
||||
</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="URL (optional)"
|
||||
value=url
|
||||
class=(if isValidUrl "valid" "")}}
|
||||
</p>
|
||||
<label>
|
||||
<p class="label">Contributor:</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<option value="" selected disabled hidden></option>
|
||||
{{#each contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Kind:</p>
|
||||
<p>
|
||||
<select required onchange={{action (mut kind) value="target.value"}}>
|
||||
<option value="" selected disabled hidden></option>
|
||||
<option value="community" selected={{eq kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
|
||||
<option value="special" selected={{eq kind "special"}}>Special</option>
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Date:</p>
|
||||
<p>
|
||||
{{ember-flatpickr
|
||||
date=date
|
||||
defaultDate=defaultDate
|
||||
maxDate=defaultDate
|
||||
enableTime=true
|
||||
time_24hr=true
|
||||
onChange=(action (mut date))
|
||||
}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Amount:</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="500"
|
||||
value=amount
|
||||
class=(if isValidAmount "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Description:</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=description
|
||||
class=(if isValidDescription "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">URL (optional):</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=url
|
||||
class=(if isValidUrl "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
|
||||
{{#if details}}
|
||||
<label>
|
||||
<p class="label">Details:</p>
|
||||
<p>
|
||||
<pre>
|
||||
{{details}}
|
||||
</pre>
|
||||
</p>
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<p class="actions">
|
||||
{{input type="submit"
|
||||
disabled=inProgress
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import { isAddress } from 'web3-utils';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
kredits: service(),
|
||||
router: service(),
|
||||
|
||||
attributes: null,
|
||||
|
||||
// TODO: add proper address validation
|
||||
isValidAccount: notEmpty('account'),
|
||||
isValidAccount: computed('account', function() {
|
||||
return isAddress(this.account);
|
||||
}),
|
||||
|
||||
isValidName: notEmpty('name'),
|
||||
isValidURL: notEmpty('url'),
|
||||
isValidGithubUID: notEmpty('github_uid'),
|
||||
isValidGithubUsername: notEmpty('github_username'),
|
||||
isValidGiteaUsername: notEmpty('gitea_username'),
|
||||
isValidWikiUsername: notEmpty('wiki_username'),
|
||||
isValidZoomDisplayName: notEmpty('zoom_display_name'),
|
||||
|
||||
isValid: and(
|
||||
'isValidAccount',
|
||||
'isValidName',
|
||||
@@ -42,7 +49,8 @@ export default Component.extend({
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
gitea_username: null,
|
||||
wiki_username: null
|
||||
wiki_username: null,
|
||||
zoom_display_name: null
|
||||
});
|
||||
},
|
||||
|
||||
@@ -70,6 +78,7 @@ export default Component.extend({
|
||||
window.alert('Something went wrong. Please check the browser console.');
|
||||
}).finally(() => {
|
||||
this.set('inProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
{{input name="wiki_username" type="text" value=wiki_username placeholder="ZeroCool"
|
||||
class=(if isValidWikiUsername "valid" "") id="c-wiki-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-zoom-display-name">Zoom display name</label>
|
||||
{{input name="zoom_display_name" type="text" value=zoom_display_name placeholder="Zero Cool"
|
||||
class=(if isValidZoomDisplayName "valid" "") id="c-zoom-display-name"}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit" disabled=inProgress
|
||||
value=(if inProgress "Processing" "Save")}}
|
||||
|
||||
@@ -6,9 +6,13 @@ import { inject as service } from '@ember/service';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
router: service(),
|
||||
|
||||
tagName: 'div',
|
||||
classNames: ['contributions'],
|
||||
|
||||
selectedContribution: null,
|
||||
|
||||
showQuickFilter: false,
|
||||
hideSmallContributions: false,
|
||||
contributorId: null,
|
||||
@@ -57,6 +61,10 @@ export default Component.extend({
|
||||
} else {
|
||||
window.alert('Only members can veto contributions. Please ask someone to set you up.');
|
||||
}
|
||||
},
|
||||
|
||||
openContributionDetails(contribution) {
|
||||
this.router.transitionTo('dashboard.contributions.show', contribution);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,17 +31,13 @@
|
||||
|
||||
<ul class="contribution-list">
|
||||
{{#each contributionsFiltered as |contribution|}}
|
||||
<li data-contribution-id={{contribution.id}} class="{{contribution-status contribution}} {{if contribution.vetoed "vetoed"}}">
|
||||
<li role="button" {{action "openContributionDetails" contribution}}
|
||||
data-contribution-id={{contribution.id}}
|
||||
class="{{contribution-status contribution}}{{if (eq contribution.id selectedContributionId) " selected"}}">
|
||||
<p class="meta">
|
||||
<span class="recipient">{{user-avatar contributor=contribution.contributor}}</span>
|
||||
<span class="category {{contribution.kind}}">({{contribution.kind}})</span>
|
||||
<span class="title">
|
||||
{{#if contribution.url}}
|
||||
<a href={{contribution.url}} target="_blank" rel="noopener" title={{contribution.description}}>{{contribution.description}}</a>
|
||||
{{else}}
|
||||
{{contribution.description}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="title">{{contribution.description}}</span>
|
||||
</p>
|
||||
<p class="kredits-amount">
|
||||
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
|
||||
@@ -49,7 +45,10 @@
|
||||
{{#unless contribution.vetoed}}
|
||||
{{#unless (is-confirmed-contribution contribution)}}
|
||||
<p class="voting">
|
||||
<button {{action "veto" contribution.id}} class="small danger">veto</button>
|
||||
{{input type="button" class="button small danger"
|
||||
click=(action "veto" contribution.id)
|
||||
disabled=contribution.hasPendingChanges
|
||||
value="veto"}}
|
||||
</p>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
@@ -7,6 +7,7 @@ export default Component.extend({
|
||||
|
||||
tagName: 'table',
|
||||
classNames: 'contributor-list',
|
||||
|
||||
selectedContributorId: null,
|
||||
|
||||
actions: {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<tbody>
|
||||
{{#each contributorList as |c|}}
|
||||
<tr role="button"
|
||||
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}"
|
||||
{{action "openContributorDetails" c.contributor}}>
|
||||
<tr role="button" {{action "openContributorDetails" c.contributor}}
|
||||
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}">
|
||||
<td class="person">
|
||||
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
||||
</td>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default Component.extend({
|
||||
account: null,
|
||||
|
||||
iconComponentName: computed('account.site', function() {
|
||||
if (this.account.site.match(/github|gitea|wiki/)) {
|
||||
if (this.account.site.match(/github|gitea|wiki|zoom/)) {
|
||||
return 'icon-account-' + this.account.site.replace(/\./g, '-');
|
||||
} else {
|
||||
return 'icon-web-globe';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg id="svg825" class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<svg class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(23.766 36.885)">
|
||||
<path d="m151.79 86.234c-7.078 0.1229-14.154 0.5605-21.232 0.7069-12.318 0.248-24.64 0.5664-36.961 0.512-11.176-0.0472-22.358-0.9728-33.518-0.7052-7.3164 0.1776-14.373 2.2948-20.131 7.584-9.9472 9.1368-12.522 21-11.314 33.807 1.92 20.357 9.2716 31.449 29.115 37.064 15.793 4.4704 31.963 5.2088 48.199 4.9744 5.254 0 10.51 0.1367 15.758-0.0292 12.723-0.4064 25.264-2.2072 37.358-6.3596 10.846-3.7225 18.799-10.691 22.572-22.166 2.6736-8.1288 3.7148-16.388 2.5544-24.853-1.7772-12.959-13.275-30.86-32.4-30.535zm-81.576 59.996c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20zm76 0c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20z" fill="none"/>
|
||||
<path class="fg" d="m200.25 59.188c-2.5076-3.4493-4.3632-6.3888-3.8748-11.44 0.6796-7.0664-0.3536-14.396-1.3204-21.518-1.2188-8.9843-3.5236-17.74-7.1776-26.23-9.0332 1.9356-17.981 3.49-25.963 8.0976-8.4648 4.8908-16.695 10.215-25.08 15.25-0.9532 0.5704-2.3124 0.8848-3.3848 0.7032-17.967-3.1368-35.92-3.0196-53.898-0.086-1.3632 0.2244-3.0724-0.2345-4.2948-0.9608-4.4436-2.6604-8.6564-5.7344-13.123-8.3516-11.656-6.844-23.66-12.822-37.494-14.59-0.3928 0.8597-0.7188 1.4785-0.9612 2.1253-4.164 11.22-6.6424 22.844-7.4276 34.82-0.2872 4.3516-0.6092 8.8048 0 13.072 0.3456 2.4296-4e-3 3.8788-1.3924 5.6132-4.8516 6.0488-8.6368 12.717-11.102 20.219-4.6972 14.295-4.4004 28.861-2.2756 43.475 1.7268 11.974 4.7344 23.537 11.324 33.873 13.186 20.682 32.892 29.774 55.572 31.705 19.308 1.6464 38.8 1.1992 58.213 1.2364 13.855 0.0251 27.57-1.4924 40.715-6.6389 20.375-7.9727 33.859-22.604 40.275-44.166 3.7344-12.545 5.2324-25.396 4.8144-38.498-0.4412-13.81-4.0504-26.566-12.144-37.711zm-18.609 82.435c-3.7736 11.475-11.727 18.444-22.572 22.166-12.094 4.1524-24.635 5.9532-37.358 6.3596-5.248 0.1659-10.504 0.0292-15.758 0.0292-16.236 0.2344-32.406-0.504-48.199-4.9744-19.844-5.6152-27.195-16.707-29.115-37.064-1.2072-12.807 1.3672-24.67 11.314-33.807 5.7576-5.2892 12.814-7.4064 20.131-7.584 11.16-0.2676 22.342 0.658 33.518 0.7052 12.32 0.0544 24.642-0.264 36.961-0.512 7.0784-0.1464 14.154-0.584 21.232-0.7069 19.125-0.3243 30.623 17.576 32.4 30.535 1.1604 8.4647 0.1192 16.724-2.5544 24.853z"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,5 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(0 -229.27)">
|
||||
<g transform="matrix(.90278 0 0 .90278 6.5684 141.05)" fill-rule="evenodd">
|
||||
<path class="fg" d="m32.67 117.1c2.2056 0.1405 3.0528 0.27041 4.1992 0.64479 3.9359 1.2854 6.7418 4.6736 7.3059 8.8223 0.06615 0.48578 0.13996 1.0253 0.16431 1.1986 0.29104 2.0743 0.07488 21.552-0.25003 22.55-0.59082 1.814-1.901 2.7993-4.0537 3.0488-1.3671 0.15849-30.564 0.0701-31.846-0.0963-5.2843-0.68659-8.8934-4.001-9.7398-8.9445-0.38418-2.2439-0.62362-20.748-0.29871-23.084 0.34422-2.4746 1.7264-3.797 4.3212-4.1338 0.98769-0.12832 28.191-0.13361 30.197-6e-3"/>
|
||||
<path class="fg" d="m61.114 118.11c0.56621 0.4072 0.97816 1.0565 1.1885 1.874 0.19553 0.75909 0.24474 28.04 0.05371 29.747-0.37333 3.3361-2.6305 4.0137-5.5264 1.6584-7.9722-6.4841-9.021-7.5734-9.6795-10.052-0.26008-0.97895-0.33152-10.629-0.08811-11.876 0.38021-1.9457 1.2594-3.1946 3.7383-5.3099 7.4491-6.3564 7.5914-6.4569 9.0522-6.4008 0.70723 0.0272 0.8599 0.0709 1.2613 0.3593"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,5 @@
|
||||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: ""
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<svg width="60px" height="60px" version="1.1" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-479,-238)" stroke="#fbe468" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
|
||||
<g transform="translate(482,244)">
|
||||
<a transform="translate(0,2)">
|
||||
<polygon id="Triangle-58" points="0.15321 44 27 0 53.847 44"/>
|
||||
</a>
|
||||
<rect x="25" y="13" width="4" height="21"/>
|
||||
<a transform="translate(0,2)">
|
||||
<circle cx="27" cy="38" r="2"/>
|
||||
</a>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 750 B |
@@ -0,0 +1,46 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
tagName: '',
|
||||
|
||||
kredits: service(),
|
||||
router: service(),
|
||||
|
||||
setupInProgress: false,
|
||||
|
||||
userHasEthereumWallet: computed(function() {
|
||||
return isPresent(window.ethereum);
|
||||
}),
|
||||
|
||||
showConnectButton: computed('userHasEthereumWallet',
|
||||
'kredits.hasAccounts', function() {
|
||||
return this.userHasEthereumWallet &&
|
||||
!this.kredits.hasAccounts;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
signup() {
|
||||
this.router.transitionTo('signup');
|
||||
},
|
||||
|
||||
async connectAccount() {
|
||||
try {
|
||||
await window.ethereum.enable();
|
||||
this.set('setupInProgress', true);
|
||||
await this.kredits.setup();
|
||||
this.set('setupInProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
} catch (error) {
|
||||
this.set('setupInProgress', false);
|
||||
console.log('Opening Ethereum wallet failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<section id="user-account">
|
||||
{{#if setupInProgress}}
|
||||
Connecting account...
|
||||
{{else}}
|
||||
{{#if (and kredits.hasAccounts kredits.currentUser)}}
|
||||
{{kredits.currentUser.name}}
|
||||
{{#if kredits.currentUserIsCore}}
|
||||
<span class="core-flag">(core)</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
Anonymous
|
||||
<button {{action "signup"}} class="small">Sign up</button>
|
||||
{{#if showConnectButton}}
|
||||
<button {{action "connectAccount"}} class="small green">Connect account</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</section>
|
||||
@@ -0,0 +1,7 @@
|
||||
import ContributionsNewController from 'kredits-web/controllers/contributions/new';
|
||||
|
||||
export default ContributionsNewController.extend({
|
||||
|
||||
attributes: null,
|
||||
|
||||
});
|
||||
@@ -8,9 +8,7 @@ export default Controller.extend({
|
||||
actions: {
|
||||
|
||||
save (attributes) {
|
||||
return this.kredits
|
||||
.updateContributor(this.model.id, attributes)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
return this.kredits.updateContributor(this.model.id, attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ export default Controller.extend({
|
||||
actions: {
|
||||
|
||||
save (contributor) {
|
||||
return this.kredits
|
||||
.addContributor(contributor)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
return this.kredits.addContributor(contributor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default Controller.extend({
|
||||
|
||||
showDetailsPane: false,
|
||||
selectedContributorId: null,
|
||||
selectedContributionId: null,
|
||||
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
@@ -7,6 +8,10 @@ export default Controller.extend({
|
||||
if (this.model.isCore) return 'Core Contributor';
|
||||
if (this.model.totalKreditsEarned <= 5000) return 'Newcomer';
|
||||
return 'Contributor';
|
||||
}),
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not } from '@ember/object/computed';
|
||||
import { isAddress } from 'web3-utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
ethAddress: null,
|
||||
githubAccessToken: alias('kredits.githubAccessToken'),
|
||||
|
||||
isValidEthAccount: computed('ethAddress', function() {
|
||||
return isAddress(this.ethAddress);
|
||||
}),
|
||||
|
||||
signupButtonDisabled: not('isValidEthAccount'),
|
||||
|
||||
actions: {
|
||||
|
||||
completeSignup () {
|
||||
const payload = {
|
||||
accessToken: this.githubAccessToken,
|
||||
account: this.ethAddress
|
||||
}
|
||||
|
||||
fetch(config.githubSignupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log('Created contributor:', data);
|
||||
|
||||
this.setProperties({
|
||||
githubAccessToken: null,
|
||||
ethAddress: null
|
||||
});
|
||||
|
||||
this.transitionToRoute('signup.complete');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import Controller from '@ember/controller';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
connectGithub () {
|
||||
window.location = config.githubConnectUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { once } from '@ember/runloop';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
@@ -8,13 +9,50 @@ export default Helper.extend({
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
this.setupRecompute(contribution);
|
||||
|
||||
let status = [];
|
||||
|
||||
if (contribution.vetoed) {
|
||||
return 'vetoed';
|
||||
status.push('vetoed');
|
||||
} else if (contribution.confirmedAt > this.currentBlock) {
|
||||
return 'unconfirmed';
|
||||
status.push('unconfirmed');
|
||||
} else {
|
||||
return 'confirmed'
|
||||
status.push('confirmed');
|
||||
}
|
||||
|
||||
if (contribution.hasPendingChanges) {
|
||||
status.push('pending');
|
||||
}
|
||||
|
||||
return status.join(' ');
|
||||
},
|
||||
|
||||
destroy () {
|
||||
if (this.teardown) this.teardown();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
setupRecompute (contribution) {
|
||||
if (this.teardown) this.teardown();
|
||||
|
||||
contribution.addObserver('vetoed' , this, this.triggerRecompute);
|
||||
contribution.addObserver('confirmedAt' , this, this.triggerRecompute);
|
||||
contribution.addObserver('currentBlock' , this, this.triggerRecompute);
|
||||
contribution.addObserver('hasPendingChanges' , this, this.triggerRecompute);
|
||||
|
||||
this.teardown = () => {
|
||||
contribution.removeObserver('vetoed', this, this.triggerRecompute);
|
||||
contribution.removeObserver('confirmedAt', this, this.triggerRecompute);
|
||||
contribution.removeObserver('currentBlock', this, this.triggerRecompute);
|
||||
contribution.removeObserver('hadPendingChanges', this, this.triggerRecompute);
|
||||
};
|
||||
},
|
||||
|
||||
triggerRecompute () {
|
||||
once(this, function () {
|
||||
this.recompute();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import EmberObject from '@ember/object';
|
||||
import EmberObject, { computed } from '@ember/object';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||
import moment from 'moment';
|
||||
|
||||
export default EmberObject.extend({
|
||||
|
||||
@@ -18,11 +20,27 @@ export default EmberObject.extend({
|
||||
description: null,
|
||||
details: null,
|
||||
url: null,
|
||||
date: null,
|
||||
time: null,
|
||||
ipfsData: '',
|
||||
|
||||
pendingTx: null,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('details', {});
|
||||
}
|
||||
if (isEmpty(this.details)) this.set('details', {});
|
||||
},
|
||||
|
||||
iso8601Date: computed('date', 'time', function() {
|
||||
return this.time ? `${this.date}T${this.time}` : this.date;
|
||||
}),
|
||||
|
||||
jsDate: computed('iso8601Date', function() {
|
||||
return moment(this.iso8601Date).toDate();
|
||||
}),
|
||||
|
||||
hasPendingChanges: computed('pendingTx', function() {
|
||||
return isPresent(this.pendingTx);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ export default EmberObject.extend({
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
wiki_username: null,
|
||||
zoom_display_name: null,
|
||||
ipfsData: ''
|
||||
|
||||
});
|
||||
|
||||
@@ -11,17 +11,27 @@ Router.map(function() {
|
||||
this.route('contributors', function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
|
||||
this.route('contributions', function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
});
|
||||
this.route('proposals', function() {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('contributions', function() {
|
||||
this.route('new');
|
||||
this.route('resubmit', { path: ':id/resubmit' });
|
||||
});
|
||||
this.route('contributors', function() {
|
||||
this.route('new');
|
||||
this.route('edit', { path: ':id/edit' });
|
||||
});
|
||||
this.route('signup', function() {
|
||||
this.route('github');
|
||||
this.route('eth-account');
|
||||
this.route('complete');
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
||||
@@ -4,7 +4,7 @@ import Route from '@ember/routing/route';
|
||||
export default Route.extend({
|
||||
kredits: service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
beforeModel(/* transition */) {
|
||||
const kredits = this.kredits;
|
||||
|
||||
return kredits.setup().then(() => {
|
||||
@@ -12,11 +12,6 @@ export default Route.extend({
|
||||
console.error('Kredits preflight check failed!');
|
||||
console.error(error);
|
||||
});
|
||||
if (kredits.get('accountNeedsUnlock')) {
|
||||
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
|
||||
transition.retry();
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log('Error initializing Kredits', error);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
model(params) {
|
||||
const contribution = this.kredits.contributions.findBy('id', parseInt(params.id));
|
||||
contribution.contributorId = contribution.contributorId.toString();
|
||||
|
||||
return contribution;
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
controller.set('attributes', model.getProperties([
|
||||
'kind', 'amount', 'description', 'url', 'details'
|
||||
]));
|
||||
controller.set('attributes.contributorId', model.contributorId.toString());
|
||||
controller.set('attributes.date', model.jsDate);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -22,7 +22,8 @@ export default Route.extend({
|
||||
github_username: model.github_username,
|
||||
github_uid: model.github_uid,
|
||||
gitea_username: model.gitea_username,
|
||||
wiki_username: model.wiki_username
|
||||
wiki_username: model.wiki_username,
|
||||
zoom_display_name: model.zoom_display_name
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { inject as service } from '@ember/service';
|
||||
import Route from '@ember/routing/route';
|
||||
import { alias } from '@ember/object/computed';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
contributions: alias('kredits.contributions'),
|
||||
|
||||
model (params) {
|
||||
return this.contributions.findBy('id', parseInt(params.id));
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: true,
|
||||
selectedContributionId: model.id
|
||||
});
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.controllerFor('dashboard')
|
||||
.setProperties({
|
||||
showDetailsPane: false,
|
||||
selectedContributionId: null
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
if (isEmpty(this.kredits.githubAccessToken)) {
|
||||
this.transitionTo('signup.index');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
let accessToken;
|
||||
try {
|
||||
accessToken = window.location.hash.match(/access_token=(.+)/)[1];
|
||||
} catch (error) { /* ignore */ }
|
||||
|
||||
if (isEmpty(accessToken) || accessToken === 'undefined') {
|
||||
console.error('No GitHub access token found.');
|
||||
this.transitionTo('signup');
|
||||
return;
|
||||
}
|
||||
|
||||
this.kredits.set('githubAccessToken', accessToken);
|
||||
|
||||
this.transitionTo('signup.eth-account');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
+59
-32
@@ -6,7 +6,7 @@ import Service from '@ember/service';
|
||||
import EmberObject from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, notEmpty } from '@ember/object/computed';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
|
||||
import groupBy from 'kredits-web/utils/group-by';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
@@ -24,15 +24,12 @@ export default Service.extend({
|
||||
contributors: null,
|
||||
contributions: null,
|
||||
proposals: null,
|
||||
githubAccessToken: null,
|
||||
|
||||
currentUserIsContributor: notEmpty('currentUser'),
|
||||
currentUserIsCore: alias('currentUser.isCore'),
|
||||
hasAccounts: notEmpty('currentUserAccounts'),
|
||||
|
||||
accountNeedsUnlock: computed('currentUserAccounts', function() {
|
||||
return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
|
||||
}),
|
||||
|
||||
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||
return this.contributions.filter(contribution => {
|
||||
return contribution.confirmedAt > this.currentBlock;
|
||||
@@ -104,7 +101,14 @@ export default Service.extend({
|
||||
async function instantiateWithAccount (web3Provider, context) {
|
||||
console.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
|
||||
ethProvider = new ethers.providers.Web3Provider(web3Provider);
|
||||
// const network = await ethProvider.getNetwork();
|
||||
|
||||
const network = await ethProvider.getNetwork();
|
||||
if (isPresent(config.web3RequiredNetwork) &&
|
||||
network.name !== config.web3RequiredNetwork) {
|
||||
window.alert(`Please switch your Ethereum wallet to the "${config.web3RequiredNetwork}" network before connecting your account.`);
|
||||
return instantiateWithoutAccount();
|
||||
}
|
||||
|
||||
ethProvider.listAccounts().then(accounts => {
|
||||
context.set('currentUserAccounts', accounts);
|
||||
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
|
||||
@@ -116,12 +120,9 @@ export default Service.extend({
|
||||
}
|
||||
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
// Request account access if needed
|
||||
await window.ethereum.enable();
|
||||
// Acccounts now exposed
|
||||
if (window.ethereum.isConnected()) {
|
||||
instantiateWithAccount(window.ethereum, this);
|
||||
} catch (error) {
|
||||
} else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
}
|
||||
@@ -209,8 +210,8 @@ export default Service.extend({
|
||||
|
||||
getContributors () {
|
||||
return this.kredits.Contributor.all()
|
||||
.then((contributors) => {
|
||||
return contributors.map((contributor) => {
|
||||
.then(contributors => {
|
||||
return contributors.map(contributor => {
|
||||
return Contributor.create(contributor);
|
||||
});
|
||||
});
|
||||
@@ -224,33 +225,37 @@ export default Service.extend({
|
||||
console.debug('[kredits] add contribution response', data);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
const contribution = Contribution.create(attributes);
|
||||
// TODO receive from wrapper
|
||||
contribution.set('pendingTx', data);
|
||||
contribution.set('confirmedAtBlock', data.blockNumber + 40320);
|
||||
this.contributions.pushObject(contribution);
|
||||
return contribution;
|
||||
});
|
||||
},
|
||||
|
||||
addProposal (attributes) {
|
||||
console.debug('[kredits] add proposal', attributes);
|
||||
//
|
||||
// TODO Implement proposals with voting
|
||||
//
|
||||
|
||||
return this.kredits.Proposal.addProposal(attributes)
|
||||
.then((data) => {
|
||||
console.debug('[kredits] add proposal response', data);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
return Proposal.create(attributes);
|
||||
});
|
||||
},
|
||||
// addProposal (attributes) {
|
||||
// console.debug('[kredits] add proposal', attributes);
|
||||
//
|
||||
// return this.kredits.Proposal.addProposal(attributes)
|
||||
// .then((data) => {
|
||||
// console.debug('[kredits] add proposal response', data);
|
||||
// attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
// return Proposal.create(attributes);
|
||||
// });
|
||||
// },
|
||||
|
||||
getProposals () {
|
||||
return this.kredits.Proposal.all()
|
||||
.then((proposals) => {
|
||||
return proposals.map((proposal) => {
|
||||
proposal.contributor = this.contributors.findBy('id', proposal.contributorId.toString());
|
||||
return Proposal.create(proposal);
|
||||
});
|
||||
});
|
||||
},
|
||||
// getProposals () {
|
||||
// return this.kredits.Proposal.all()
|
||||
// .then(proposals => {
|
||||
// return proposals.map(proposal => {
|
||||
// proposal.contributor = this.contributors.findBy('id', proposal.contributorId.toString());
|
||||
// return Proposal.create(proposal);
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
|
||||
getContributions () {
|
||||
return this.kredits.Contribution.all({page: {size: 200}})
|
||||
@@ -274,10 +279,12 @@ export default Service.extend({
|
||||
|
||||
veto (contributionId) {
|
||||
console.debug('[kredits] veto against', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
|
||||
return this.kredits.Contribution.functions.veto(contributionId, { gasLimit: 300000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] veto response', data);
|
||||
contribution.set('pendingTx', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
@@ -310,6 +317,7 @@ export default Service.extend({
|
||||
.on('ContributorAdded', this.handleContributorChange.bind(this))
|
||||
|
||||
this.kredits.Contribution
|
||||
.on('ContributionAdded', this.handleContributionAdded.bind(this))
|
||||
.on('ContributionVetoed', this.handleContributionVetoed.bind(this))
|
||||
|
||||
this.kredits.Proposal
|
||||
@@ -337,6 +345,24 @@ export default Service.extend({
|
||||
this.contributors.pushObject(newContributor);
|
||||
},
|
||||
|
||||
async handleContributionAdded (id, contributorId, amount) {
|
||||
console.debug('[kredits] ContributionAdded event received', { id, contributorId, amount });
|
||||
|
||||
const pendingContribution = this.contributions.find(c => {
|
||||
return (c.id === null) &&
|
||||
(c.contributorId.toString() === contributorId.toString()) &&
|
||||
(c.amount.toString() === amount.toString());
|
||||
});
|
||||
|
||||
if (pendingContribution) {
|
||||
const attributes = await this.kredits.Contribution.getById(id);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId.toString());
|
||||
const newContribution = Contribution.create(attributes);
|
||||
this.contributions.addObject(newContribution);
|
||||
this.contributions.removeObject(pendingContribution);
|
||||
}
|
||||
},
|
||||
|
||||
handleContributionVetoed (contributionId) {
|
||||
console.debug('[kredits] ContributionVetoed event received for ', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
@@ -344,6 +370,7 @@ export default Service.extend({
|
||||
|
||||
if (contribution) {
|
||||
contribution.set('vetoed', true);
|
||||
contribution.set('pendingTx', null);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -23,12 +23,16 @@ button, input[type=submit], .button {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.86rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
&.danger:not(:disabled) {
|
||||
color: $red;
|
||||
background-color: rgba(40, 21, 21, 0.6);
|
||||
border-color: rgba(40, 21, 21, 1);
|
||||
@@ -41,7 +45,7 @@ button, input[type=submit], .button {
|
||||
}
|
||||
}
|
||||
|
||||
&.green {
|
||||
&.green:not(:disabled) {
|
||||
color: $green;
|
||||
background-color: rgba(21, 40, 21, 0.6);
|
||||
border-color: rgba(21, 40, 21, 1);
|
||||
@@ -53,4 +57,17 @@ button, input[type=submit], .button {
|
||||
border-color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
&.icon {
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 1rem;
|
||||
|
||||
.fg {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -1,12 +1,21 @@
|
||||
section#add-contributor,
|
||||
section#add-contribution,
|
||||
section#add-proposal {
|
||||
section#add-proposal,
|
||||
section#signup {
|
||||
|
||||
form {
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.label {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
padding-top: 1.5rem;
|
||||
text-align: center;
|
||||
|
||||
@@ -28,6 +28,37 @@ main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
.content {
|
||||
a {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
&.text-lg {
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
|
||||
+12
-3
@@ -2,7 +2,6 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
*:hover, *:active, *:focus {
|
||||
@@ -17,13 +16,17 @@
|
||||
|
||||
$font-family-sans: 'Open Sans', sans-serif;
|
||||
|
||||
body {
|
||||
html, body {
|
||||
font-family: $font-family-sans;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: $body-text-color;
|
||||
}
|
||||
|
||||
body {
|
||||
background-image: linear-gradient(to bottom, rgba(22, 21, 40, .4), rgba(0, 0, 0, .75)), url('/img/bg.jpg');
|
||||
background-repeat: none;
|
||||
background-attachment: fixed;
|
||||
color: $body-text-color;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
@@ -38,6 +41,10 @@ h1, h2, h3, h4, h5, input, button {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
section {
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
@@ -99,10 +106,12 @@ section {
|
||||
@import "forms";
|
||||
|
||||
@import "components/contribution-list";
|
||||
@import "components/contribution-details";
|
||||
@import "components/contributor-list";
|
||||
@import "components/contributor-profile";
|
||||
@import "components/external-account-link";
|
||||
@import "components/loading-spinner";
|
||||
@import "components/proposal-list";
|
||||
@import "components/topbar";
|
||||
@import "components/topbar-account-panel";
|
||||
@import "components/user-avatar";
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
section#contribution-details {
|
||||
header {
|
||||
nav {
|
||||
.amount {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
padding-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
margin: 0 0 1.5rem;
|
||||
padding: 2rem 2rem;
|
||||
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
|
||||
h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
span {
|
||||
}
|
||||
|
||||
&.who-what-when {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.button) {
|
||||
color: $primary-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:not(.button):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.vetoed {
|
||||
.content {
|
||||
h3 {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint.vetoed {
|
||||
overflow: auto;
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
font-size: 1.2rem;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,13 +11,10 @@ main section {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
p {
|
||||
font-size: inherit;
|
||||
padding: 0.2rem 0 0;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: inherit;
|
||||
|
||||
&+ label {
|
||||
margin-left: 3.6rem;
|
||||
}
|
||||
@@ -37,6 +34,7 @@ ul.contribution-list {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
font-size: 1.2rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.2);
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type {
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
@@ -52,10 +50,13 @@ ul.contribution-list {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -67,11 +68,8 @@ ul.contribution-list {
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
|
||||
&.title {
|
||||
a {
|
||||
font-size: inherit;
|
||||
text-decoration: none;
|
||||
color: $body-text-color;
|
||||
}
|
||||
@@ -146,9 +144,7 @@ ul.contribution-list {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.description {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ ul.proposal-list {
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
|
||||
&.kredits-amount, &.voting {
|
||||
@@ -36,7 +35,6 @@ ul.proposal-list {
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.description {
|
||||
@@ -107,7 +105,6 @@ ul.proposal-list {
|
||||
|
||||
|
||||
.description {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
header#topbar section#user-account {
|
||||
|
||||
button {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
button + button {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,16 +1,6 @@
|
||||
<header id="topbar">
|
||||
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1>
|
||||
|
||||
<section id="user-account">
|
||||
{{#if kredits.hasAccounts }}
|
||||
{{#if kredits.currentUser}}
|
||||
{{kredits.currentUser.name}}
|
||||
{{#if kredits.currentUserIsCore}}(core){{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
Anonymous
|
||||
{{/if}}
|
||||
</section>
|
||||
{{topbar-account-panel}}
|
||||
</header>
|
||||
|
||||
{{outlet}}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contribution">
|
||||
<header>
|
||||
<h2>Re-submit contribution #{{model.id}}</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contribution attributes=attributes contributors=sortedContributors save=(action "save")}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -53,6 +53,7 @@
|
||||
{{contribution-list contributions=contributionsUnconfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
contractInteractionEnabled=kredits.hasAccounts
|
||||
selectedContributionId=selectedContributionId
|
||||
showQuickFilter=showQuickFilterUnconfirmed}}
|
||||
</div>
|
||||
</section>
|
||||
@@ -68,6 +69,7 @@
|
||||
<div class="content">
|
||||
{{contribution-list contributions=contributionsConfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
selectedContributionId=selectedContributionId
|
||||
showQuickFilter=showQuickFilterConfirmed}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<section id="contribution-details" class={{if model.vetoed "vetoed"}}>
|
||||
<header class="with-nav">
|
||||
<h2>Contribution #{{model.id}}</h2>
|
||||
<nav>
|
||||
<span class="amount">{{model.amount}}</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<p class="who-what-when">
|
||||
<span class="contributor">
|
||||
{{user-avatar contributor=model.contributor}}
|
||||
{{link-to model.contributor.name "dashboard.contributors.show" model.contributor}}
|
||||
</span>
|
||||
contributed
|
||||
<span class="date" title={{model.iso8601Date}}>{{moment-from model.iso8601Date}}</span>:
|
||||
</p>
|
||||
<h3>{{model.description}}</h3>
|
||||
<p>
|
||||
Kind: {{model.kind}}
|
||||
<br>Status: {{contribution-status model}}
|
||||
</p>
|
||||
{{#if model.url}}
|
||||
<p>
|
||||
<a href={{model.url}}
|
||||
title={{model.description}}
|
||||
class="button"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
Open URL
|
||||
</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{#if model.vetoed}}
|
||||
<div class="hint vetoed">
|
||||
<div class="icon">
|
||||
{{icon-warning}}
|
||||
</div>
|
||||
<p>
|
||||
This contribution has been vetoed, meaning no
|
||||
kredits will be issued.
|
||||
</p>
|
||||
<p>
|
||||
{{link-to "Re-submit contribution …" "contributions.resubmit" model class="button small green"}}.
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<p>
|
||||
{{link-to "Copy & edit as new" "contributions.resubmit" model class="button small"}}.
|
||||
{{#if model.ipfsHash}}
|
||||
<a href="{{ipfsGatewayUrl}}/{{model.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener">
|
||||
Inspect IPFS data
|
||||
</a>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -28,7 +28,7 @@
|
||||
<p>
|
||||
<a href="https://rinkeby.etherscan.io/address/{{model.account}}" class="button small" target="_blank" rel="noopener">Inspect Ethereum transactions</a>
|
||||
{{#if model.ipfsHash}}
|
||||
<a href="https://ipfs.io/ipfs/{{model.ipfsHash}}" class="button small" target="_blank" rel="noopener">Inspect IPFS profile</a>
|
||||
<a href="{{ipfsGatewayUrl}}/{{model.ipfsHash}}" class="button small" target="_blank" rel="noopener">Inspect IPFS profile</a>
|
||||
{{/if}}
|
||||
{{link-to "Edit profile" "contributors.edit" model class="button small"}}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<main class="center-column">
|
||||
<section id="signup">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</main>
|
||||
@@ -0,0 +1,15 @@
|
||||
<header>
|
||||
<h2>Welcome aboard!</h2>
|
||||
</header>
|
||||
<div class="content text-lg text-center">
|
||||
<p>
|
||||
Congratulations. Your initial profile is now complete.
|
||||
</p>
|
||||
<p>
|
||||
Why not say hi to your fellow contributors
|
||||
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved">in one of our chat rooms</a>?.
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{#link-to "dashboard" class="button small"}}Return to dashboard{{/link-to}}
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
<header>
|
||||
<h2>Complete your contributor profile</h2>
|
||||
</header>
|
||||
<div class="content text-lg">
|
||||
<p class="mg-bottom-md">
|
||||
Kredits allow you to take part in project governance, and to earn rewards for
|
||||
your contributions. For both, you will need an Ethereum wallet/account.
|
||||
</p>
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<p>
|
||||
<label>
|
||||
Ethereum account:<br>
|
||||
{{input type="text" value=ethAddress
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
class=(if isValidEthAccount "valid" "")}}
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
<p class="actions">
|
||||
<button disabled={{signupButtonDisabled}}
|
||||
{{action "completeSignup"}}>
|
||||
Complete my profile
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<header>
|
||||
<h2>Create your contributor profile</h2>
|
||||
</header>
|
||||
<div class="content text-lg">
|
||||
<p>
|
||||
You have already contributed to a Kosmos project, or you are interested
|
||||
in contributing in the near future? Fantastic!
|
||||
</p>
|
||||
<p>
|
||||
In order to earn kredits for your contributions, you will need a
|
||||
registered contributor profile. The quickest way to register is by
|
||||
connecting one of the following accounts:
|
||||
</p>
|
||||
<p class="actions">
|
||||
<button {{action "connectGithub"}} class="icon">
|
||||
{{icon-account-github-com}}
|
||||
Connect GitHub
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
+11
-8
@@ -12,7 +12,7 @@ module.exports = function(environment) {
|
||||
EmberENV: {
|
||||
FEATURES: {
|
||||
// Here you can enable experimental features on an ember canary build
|
||||
// e.g. 'with-controller': true
|
||||
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
|
||||
},
|
||||
EXTEND_PROTOTYPES: {
|
||||
// Prevent Ember Data from overriding Date.parse.
|
||||
@@ -36,6 +36,10 @@ module.exports = function(environment) {
|
||||
},
|
||||
|
||||
web3ProviderUrl: 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9',
|
||||
web3RequiredNetwork: 'rinkeby',
|
||||
|
||||
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github',
|
||||
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github',
|
||||
|
||||
ipfs: {
|
||||
host: 'ipfs.kosmos.org',
|
||||
@@ -53,6 +57,9 @@ module.exports = function(environment) {
|
||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||
ENV.web3ProviderUrl = 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9';
|
||||
|
||||
ENV.githubConnectUrl = 'http://localhost:8888/kredits/signup/connect/github';
|
||||
ENV.githubSignupUrl = 'http://localhost:8888/kredits/signup/github';
|
||||
|
||||
ENV.ipfs = {
|
||||
host: 'localhost',
|
||||
port: '5001',
|
||||
@@ -73,16 +80,12 @@ module.exports = function(environment) {
|
||||
ENV.APP.autoboot = false;
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
// here you can enable a production-specific feature
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN || 'open.aragonpm.eth';
|
||||
}
|
||||
|
||||
if (process.env.WEB3_PROVIDER_URL) {
|
||||
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||
ENV.web3RequiredNetwork = null;
|
||||
}
|
||||
if (process.env.KREDITS_KERNEL_ADDRESS) {
|
||||
ENV.kreditsKernelAddress = process.env.KREDITS_KERNEL_ADDRESS;
|
||||
if (process.env.KREDITS_DAO_ADDRESS) {
|
||||
ENV.kreditsKernelAddress = process.env.KREDITS_DAO_ADDRESS;
|
||||
}
|
||||
if (process.env.KREDITS_APM_DOMAIN) {
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN;
|
||||
|
||||
Generated
+1861
-5617
File diff suppressed because it is too large
Load Diff
+24
-21
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"version": "1.5.1",
|
||||
"version": "1.12.0",
|
||||
"private": true,
|
||||
"description": "Contribution dashboard of the Kosmos project",
|
||||
"repository": "https://github.com/67P/kredits-web",
|
||||
@@ -14,8 +14,8 @@
|
||||
"lint:hbs": "ember-template-lint .",
|
||||
"lint:js": "eslint .",
|
||||
"test": "ember test",
|
||||
"start": "KREDITS_APM_DOMAIN=open.aragonpm.eth ember serve",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember s",
|
||||
"start": "ember serve",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember serve",
|
||||
"build": "ember build",
|
||||
"build-prod": "rm -rf release/* && ember build -prod --output-path release",
|
||||
"preversion": "npm test",
|
||||
@@ -24,48 +24,51 @@
|
||||
"deploy": "git push 5apps master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/jquery": "^0.5.2",
|
||||
"@ember/optional-features": "^0.6.3",
|
||||
"@ember/jquery": "^0.6.0",
|
||||
"@ember/optional-features": "^0.7.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babelify": "^7.3.0",
|
||||
"brfs": "^2.0.2",
|
||||
"broccoli-asset-rev": "^2.7.0",
|
||||
"ember-ajax": "^4.0.1",
|
||||
"ember-auto-import": "^1.5.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"ember-ajax": "^5.0.0",
|
||||
"ember-auto-import": "^1.5.3",
|
||||
"ember-awesome-macros": "0.41.0",
|
||||
"ember-cli": "~3.8.1",
|
||||
"ember-cli": "~3.10.1",
|
||||
"ember-cli-app-version": "^3.2.0",
|
||||
"ember-cli-babel": "^7.1.2",
|
||||
"ember-cli-babel": "^7.7.3",
|
||||
"ember-cli-chart": "^3.4.0",
|
||||
"ember-cli-dependency-checker": "^3.1.0",
|
||||
"ember-cli-eslint": "^4.2.3",
|
||||
"ember-cli-htmlbars": "^3.0.0",
|
||||
"ember-cli-htmlbars-inline-precompile": "^1.0.3",
|
||||
"ember-cli-eslint": "^5.1.0",
|
||||
"ember-cli-htmlbars": "^3.0.1",
|
||||
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
|
||||
"ember-cli-inject-live-reload": "^1.8.2",
|
||||
"ember-cli-moment-shim": "^3.7.1",
|
||||
"ember-cli-sass": "^7.2.0",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-template-lint": "^1.0.0-beta.1",
|
||||
"ember-cli-uglify": "^2.1.0",
|
||||
"ember-cli-update": "^0.21.2",
|
||||
"ember-export-application-global": "^2.0.0",
|
||||
"ember-flatpickr": "^2.14.0",
|
||||
"ember-load-initializers": "^1.1.0",
|
||||
"ember-load-initializers": "^2.0.0",
|
||||
"ember-macro-helpers": "0.17.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
"ember-moment": "^7.8.1",
|
||||
"ember-promise-helpers": "1.0.6",
|
||||
"ember-qunit": "^3.4.1",
|
||||
"ember-qunit": "^4.4.1",
|
||||
"ember-resolver": "^5.0.1",
|
||||
"ember-source": "~3.8.0",
|
||||
"ember-source": "~3.10.0",
|
||||
"ember-truth-helpers": "github:jmurphyau/ember-truth-helpers#31a14373a31f1f82c77537720549b47a95c28e5f",
|
||||
"eslint-plugin-ember": "^5.2.0",
|
||||
"eslint-plugin-ember": "^6.2.0",
|
||||
"eslint-plugin-node": "^9.0.1",
|
||||
"ethers": "^4.0.27",
|
||||
"kosmos-schemas": "^2.0.0",
|
||||
"kredits-contracts": "^5.4.0",
|
||||
"kredits-contracts": "^5.5.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"ndjson": "github:hugomrdias/ndjson#feat/readable-stream3",
|
||||
"qunit-dom": "^0.8.0",
|
||||
"qunit-dom": "^0.8.4",
|
||||
"transform-loader": "^0.2.4",
|
||||
"tv4": "^1.3.0"
|
||||
"tv4": "^1.3.0",
|
||||
"web3-utils": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8.* || >= 10.*"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+6749
-4702
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4
-4
@@ -8,10 +8,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
|
||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.5.1%2Bc6588097%22%7D%2C%22browserify%22%3A%7B%22tests%22%3Atrue%2C%22transform%22%3A%5B%5B%22babelify%22%2C%7B%22presets%22%3A%5B%22es2015%22%5D%2C%22global%22%3Atrue%7D%5D%5D%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frinkeby.infura.io%2Fv3%2Fd4f788b7a6584f7db2fc3c268d4d09e9%22%2C%22ipfs%22%3A%7B%22host%22%3A%22ipfs.kosmos.org%22%2C%22port%22%3A%225444%22%2C%22protocol%22%3A%22https%22%2C%22gatewayUrl%22%3A%22https%3A%2F%2Fipfs.kosmos.org%2Fipfs%22%7D%2C%22kreditsApmDomain%22%3A%22open.aragonpm.eth%22%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_JQUERY_INTEGRATION%22%3Atrue%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.12.0%2B5a618f7e%22%7D%2C%22browserify%22%3A%7B%22tests%22%3Atrue%2C%22transform%22%3A%5B%5B%22babelify%22%2C%7B%22presets%22%3A%5B%22es2015%22%5D%2C%22global%22%3Atrue%7D%5D%5D%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frinkeby.infura.io%2Fv3%2Fd4f788b7a6584f7db2fc3c268d4d09e9%22%2C%22web3RequiredNetwork%22%3A%22rinkeby%22%2C%22githubConnectUrl%22%3A%22https%3A%2F%2Fhal8000.chat.kosmos.org%2Fkredits%2Fsignup%2Fconnect%2Fgithub%22%2C%22githubSignupUrl%22%3A%22https%3A%2F%2Fhal8000.chat.kosmos.org%2Fkredits%2Fsignup%2Fgithub%22%2C%22ipfs%22%3A%7B%22host%22%3A%22ipfs.kosmos.org%22%2C%22port%22%3A%225444%22%2C%22protocol%22%3A%22https%22%2C%22gatewayUrl%22%3A%22https%3A%2F%2Fipfs.kosmos.org%2Fipfs%22%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||
|
||||
<link integrity="" rel="stylesheet" href="/assets/vendor-179cf27a97ea5bbb0ce72403a401cc8c.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-d27bdcdf554d9b3bd6b4d37dd84f4db2.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-a1a46af359f6abc5253690f6a7b29c6b.css">
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<body>
|
||||
|
||||
|
||||
<script src="/assets/vendor-13953d1578bf9c8e1d4dec9854bc4804.js" integrity="sha256-F5T7/NqJV3S3xep1UpKy/oQii+9YrqCry6EiZxhkzOA= sha512-y4Kk91N4T4PdPgjLiBRJV7q8XFmQY9Dh5ygJayeRcMRBueWJCe4OPDnrstcJ8C/cjGePUGOFoTmMSbF0wWTB+w==" ></script>
|
||||
<script src="/assets/kredits-web-686d8b7af100292e31164c098c81d9fe.js" integrity="sha256-HjLTIO3FCh5VYjuLTlJEx4Ek3OX9fcpEpnNTVYjmiXs= sha512-NQqT/Mx6eFztNGI+lmRPYo/cfDSGKHLtttfzh69EV6daCMLtJUW1NfI4xNpErMadPudRMOAG3R5Uis7+qLoJjw==" ></script>
|
||||
<script src="/assets/vendor-21c31ba5343c7b72f638899d335a0531.js" integrity="sha256-5QKR4/TvbS9UoThtAlcyvj54wU+Bcn0hnM0FzjUbvfA= sha512-tHxWM5t9FYFz6DBnXuzWiIMPPW/gNwPaFDOdUL32cWvWcMfjyrhIEPM5I1D6+HQE1kXCEd64a+7UiMdU2hH5JQ==" ></script>
|
||||
<script src="/assets/kredits-web-afbc29b08c72e5915f60326c02c8aaf0.js" integrity="sha256-7NimDs4gj3uLie/5x8Y5dQZDjLbYaxqwWOn8eCQMZ84= sha512-L4P1fbUTM9zXge/og2OyK7pKHEG81XUFSWJMeBfMEvamlttklZqZCLXHHfrKyEip8RKPX1/ud7BUGvw01weptQ==" ></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
module('Integration | Component | topbar-account-panel', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('unknown user without wallet (or no permission to get wallet/account info)', async function(assert) {
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
|
||||
});
|
||||
|
||||
test('unknown user with Ethereum wallet', async function(assert) {
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('currentUserAccounts', [{ foo: 'bar' }]);
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
|
||||
});
|
||||
|
||||
test('known contributor', async function(assert) {
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('currentUserAccounts', [{ foo: 'bar' }]);
|
||||
service.set('currentUser', {
|
||||
name: 'Dorian Nakamoto',
|
||||
isCore: false
|
||||
});
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), 'Dorian Nakamoto');
|
||||
|
||||
service.set('currentUser.isCore', true);
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.equal(this.element.querySelectorAll('span.core-flag').length, 1);
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ module('Integration | Component | user-avatar', function(hooks) {
|
||||
this.set('bumi', contributors.findBy('id', '1'));
|
||||
await render(hbs`{{user-avatar contributor=bumi}}`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
assert.dom(this.element).hasText('');
|
||||
});
|
||||
|
||||
test('default image source URL', async function(assert) {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import Contribution from 'kredits-web/models/contribution';
|
||||
|
||||
module('Unit | Helper | contribution-status', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('returns the appropriate status', function (assert) {
|
||||
const contributionStatus = this.owner.factoryFor('helper:contribution-status').create();
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
|
||||
kredits.set('currentBlock', 23000);
|
||||
|
||||
const contributionUnconfirmed = Contribution.create({ confirmedAt: 23001, vetoed: false });
|
||||
const contributionConfirmed = Contribution.create({ confirmedAt: 21000, vetoed: false });
|
||||
const contributionVetoed = Contribution.create({ confirmedAt: 23001, vetoed: true });
|
||||
|
||||
assert.equal(contributionStatus.compute([contributionUnconfirmed]), 'unconfirmed');
|
||||
assert.equal(contributionStatus.compute([contributionConfirmed]), 'confirmed');
|
||||
assert.equal(contributionStatus.compute([contributionVetoed]), 'vetoed');
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Helper | contribution-status', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('returns the appropriate status', function (assert) {
|
||||
const contributionStatus = this.owner.factoryFor('helper:contribution-status').create();
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
|
||||
kredits.set('currentBlock', 23000);
|
||||
|
||||
const contributionUnconfirmed = { confirmedAt: 23001, vetoed: false };
|
||||
const contributionConfirmed = { confirmedAt: 21000, vetoed: false };
|
||||
const contributionVetoed = { confirmedAt: 23001, vetoed: true };
|
||||
|
||||
assert.eq(contributionStatus.compute([contributionUnconfirmed]), 'unconfirmed');
|
||||
assert.eq(contributionStatus.compute([contributionConfirmed]), 'confirmed');
|
||||
assert.eq(contributionStatus.compute([contributionVetoed]), 'vetoed');
|
||||
});
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Helper | contribution-status', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('returns the appropriate status', function (assert) {
|
||||
const contributionStatus = this.owner.factoryFor('helper:contribution-status').create();
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
|
||||
kredits.set('currentBlock', 23000);
|
||||
|
||||
const contributionUnconfirmed = { confirmedAt: 23001, vetoed: false };
|
||||
const contributionConfirmed = { confirmedAt: 21000, vetoed: false };
|
||||
const contributionVetoed = { confirmedAt: 23001, vetoed: true };
|
||||
|
||||
assert.notOk(contributionStatus.compute([contributionUnconfirmed]), 'unconfirmed');
|
||||
assert.notOk(contributionStatus.compute([contributionVetoed]), 'vetoed');
|
||||
assert.ok(contributionStatus.compute([contributionConfirmed]), 'confirmed');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import Contribution from 'kredits-web/models/contribution';
|
||||
|
||||
module('Unit | Model | contribution', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('iso8601Date', function(assert) {
|
||||
const model = Contribution.create({
|
||||
date: '2019-09-10'
|
||||
});
|
||||
assert.equal(model.iso8601Date, '2019-09-10');
|
||||
|
||||
model.set('time', '09:33:00.141Z');
|
||||
assert.equal(model.iso8601Date, '2019-09-10T09:33:00.141Z');
|
||||
});
|
||||
|
||||
test('jsDate', function(assert) {
|
||||
const model = Contribution.create({
|
||||
date: '2019-09-10',
|
||||
time: '09:33:00.141Z'
|
||||
});
|
||||
|
||||
assert.ok(model.jsDate instanceof Date);
|
||||
assert.equal(model.jsDate.toISOString(), '2019-09-10T09:33:00.141Z');
|
||||
});
|
||||
|
||||
test('hasPendingChanges', function(assert) {
|
||||
const model = Contribution.create({});
|
||||
assert.equal(model.hasPendingChanges, false);
|
||||
|
||||
model.set('pendingTx', { hash: 'abcdef123456' });
|
||||
assert.equal(model.hasPendingChanges, true);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | contributions/resubmit', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:contributions/resubmit');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | dashboard/contributions/show', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:dashboard/contributions/show');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | signup', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:signup');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | signup/complete', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:signup/complete');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | signup/eth-account', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:signup/eth-account');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
@@ -5,37 +5,37 @@ import { A } from '@ember/array';
|
||||
import { module, test } from 'qunit';
|
||||
import groupBy from 'kredits-web/utils/group-by';
|
||||
|
||||
module('Unit | Utils | group-by');
|
||||
module('Unit | Utils | group-by', function () {
|
||||
let car1 = { name: 'Carrera', color: 'red' };
|
||||
let car2 = { name: 'Veyron', color: 'red' };
|
||||
let car3 = { name: 'Corvette', color: 'blue' };
|
||||
let car4 = { name: 'Viper', color: 'blue' };
|
||||
let car5 = { name: 'Cobra', color: 'green' };
|
||||
let cars = A([car1, car2, car3, car4, car5]);
|
||||
|
||||
let car1 = { name: 'Carrera', color: 'red' };
|
||||
let car2 = { name: 'Veyron', color: 'red' };
|
||||
let car3 = { name: 'Corvette', color: 'blue' };
|
||||
let car4 = { name: 'Viper', color: 'blue' };
|
||||
let car5 = { name: 'Cobra', color: 'green' };
|
||||
let cars = A([car1, car2, car3, car4, car5]);
|
||||
test('it groups cars by color', function (assert) {
|
||||
assert.expect(1);
|
||||
let redGroup = { property: 'color', value: 'red', items: [car1, car2] };
|
||||
let blueGroup = { property: 'color', value: 'blue', items: [car3, car4] };
|
||||
let greenGroup = { property: 'color', value: 'green', items: [car5] };
|
||||
|
||||
test('it groups cars by color', function(assert) {
|
||||
assert.expect(1);
|
||||
let redGroup = { property: 'color', value: 'red', items: [car1, car2] };
|
||||
let blueGroup = { property: 'color', value: 'blue', items: [car3, car4] };
|
||||
let greenGroup = { property: 'color', value: 'green', items: [car5] };
|
||||
let result = groupBy(cars, 'color');
|
||||
let expected = [redGroup, blueGroup, greenGroup];
|
||||
|
||||
let result = groupBy(cars, 'color');
|
||||
let expected = [redGroup, blueGroup, greenGroup];
|
||||
assert.deepEqual(result, expected);
|
||||
});
|
||||
|
||||
assert.deepEqual(result, expected);
|
||||
});
|
||||
|
||||
test('it does not fail with empty array', function(assert) {
|
||||
let cars = [];
|
||||
|
||||
let result = groupBy(cars, 'color');
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
test('it does not failkwith null', function(assert) {
|
||||
let cars = null;
|
||||
|
||||
let result = groupBy(cars, 'color');
|
||||
assert.deepEqual(result, []);
|
||||
test('it does not fail with empty array', function (assert) {
|
||||
let cars = [];
|
||||
|
||||
let result = groupBy(cars, 'color');
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
|
||||
test('it does not failkwith null', function (assert) {
|
||||
let cars = null;
|
||||
|
||||
let result = groupBy(cars, 'color');
|
||||
assert.deepEqual(result, []);
|
||||
});
|
||||
});
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:sketch="http://www.bohemiancoding.com/sketch/ns"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg15"
|
||||
version="1.1"
|
||||
viewBox="0 0 60 60"
|
||||
height="60px"
|
||||
width="60px">
|
||||
<metadata
|
||||
id="metadata19">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Warning</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
|
||||
<title
|
||||
id="title2">Warning</title>
|
||||
<desc
|
||||
id="desc4">Created with Sketch.</desc>
|
||||
<defs
|
||||
id="defs6" />
|
||||
<g
|
||||
style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1"
|
||||
sketch:type="MSPage"
|
||||
id="stroked">
|
||||
<g
|
||||
style="stroke:#535353;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||
transform="translate(-479,-238)"
|
||||
sketch:type="MSLayerGroup"
|
||||
id="Transport">
|
||||
<g
|
||||
sketch:type="MSShapeGroup"
|
||||
transform="translate(482,244)"
|
||||
id="Warning">
|
||||
<a
|
||||
transform="translate(0,2)"
|
||||
id="a4529">
|
||||
<polygon
|
||||
id="Triangle-58"
|
||||
points="0.15321248,44 27,0 53.846787,44 " />
|
||||
</a>
|
||||
<rect
|
||||
height="21"
|
||||
width="4"
|
||||
y="13"
|
||||
x="25"
|
||||
id="Rectangle-1700" />
|
||||
<a
|
||||
transform="translate(0,2)"
|
||||
id="a4532">
|
||||
<circle
|
||||
id="Oval-1484"
|
||||
cx="27"
|
||||
cy="38"
|
||||
r="2" />
|
||||
</a>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
Reference in New Issue
Block a user