Compare commits

...

60 Commits

Author SHA1 Message Date
basti 174073fc42 1.17.0 2020-06-02 11:55:29 +02:00
basti 733e5e3683 Merge pull request #191 from 67P/chore/update_dependencies
Update dependencies
2020-06-02 11:52:10 +02:00
basti 090d1d0856 Update dependencies
Fixes a bunch of security audit warnings in the process.
2020-06-02 11:25:35 +02:00
basti e047cc45ba Merge pull request #190 from 67P/feature/improve_contribution_sync
Improve data sync
2020-06-02 10:55:36 +02:00
basti c64a503546 Remove unused async from function
Throws a linter warning, now that it's not an RSVP promise anymore.
2020-05-31 12:46:51 +02:00
basti 13021d665d Merge pull request #188 from 67P/feature/schema_updates
Add new contribution kinds
2020-05-31 10:45:16 +02:00
basti b5dbb2449d Merge pull request #189 from 67P/feature/data_loading
Cache data in browser storage, and load from local cache when present
2020-05-31 10:44:36 +02:00
basti 302c3315a3 Update debug logs while fetching past contributions 2020-05-29 22:28:42 +02:00
basti bac4c1c425 Load all past contributions once everything else is loaded 2020-05-29 22:04:03 +02:00
basti c54d754c89 Only sync contributions if loaded from cache 2020-05-29 20:55:19 +02:00
basti 63dd5430bc WIP Sync contributions 2020-05-29 18:56:05 +02:00
basti eb2c613ac9 Extract data loading from fetch methods 2020-05-29 13:12:39 +02:00
basti bf6d9dbc31 Remove RSVP from kredits service
Just native Promise will do.
2020-05-29 13:11:36 +02:00
basti c491f10ec3 Remove obsolete utils 2020-05-28 14:18:30 +02:00
basti d4b546bfcd Load contributions from cache if present
And then fetch more from network. Also, only load 30 from network before
first render (with nothing cached), then load more after the page has
rendered.
2020-05-28 13:59:10 +02:00
basti 69141a31c4 Process contribution data before loading models 2020-05-28 13:44:32 +02:00
basti e7c9620bbe Add network namespace for browser cache
We want to be able to cache data from multiple networks side-by-side,
without them interfering with each other.
2020-05-28 11:53:19 +02:00
basti 832de3c06f Load contributors from cache if present
And then update from network later
2020-05-28 11:33:47 +02:00
basti 50e1fc728c Cache contributors in IndexedDB 2020-05-27 17:33:36 +02:00
basti 871731110b Process contributor data
Add util function for processing contributor data, and remove bignums,
etc.
2020-05-27 16:27:08 +02:00
basti f996c89dfe Add localforage, basic cache service 2020-05-27 12:59:37 +02:00
basti 1d8e9ddf5d Remove obsolete npm cache Travis config
It's cached by default now
2020-05-27 11:19:23 +02:00
basti 352c9fe3ab Add new contribution types 2020-05-27 11:15:42 +02:00
basti e2a944ed5b Update schemas package
Use the new one from the org namespace.
2020-05-16 13:21:02 +02:00
basti c57d4162d7 1.16.0 2020-05-16 11:28:23 +02:00
basti 41cc37d3eb Merge pull request #186 from 67P/chore/upgrade_ember_and_deps
Upgrade Ember and deps to 3.18/Octane
2020-05-11 13:18:58 +02:00
basti 081584a508 Remove obsolete file 2020-05-11 13:15:47 +02:00
basti 3cc01cce4e Fix some minor template issues 2020-05-11 13:15:17 +02:00
basti cedbe3cb3d Formatting 2020-05-11 13:03:46 +02:00
basti b8da04c145 Re-add contribution status class 2020-05-11 13:03:46 +02:00
galfert 009caa53e5 Fix typo in contribution-status helper
The wrong property name caused an exception when trying to remove the observer.
2020-05-11 12:18:07 +02:00
basti 9a1f543921 Mark remaining template-lint issues as pending
These need more updates outside of the templates, like e.g. Glimmer
components and such.
2020-05-11 10:24:38 +02:00
basti 22300c2980 Fix actions, etc. 2020-05-11 10:21:23 +02:00
basti 018132034d Fix github icon filename 2020-05-11 10:21:23 +02:00
basti f5d7916556 Turn on warnings for observer usage 2020-05-11 10:21:23 +02:00
basti 12df28f7fd Fix ESLint errors 2020-05-11 10:21:22 +02:00
basti 2f4f350a93 WIP Octanify the templates 2020-05-11 10:21:18 +02:00
basti 4946b3311d Remove all proposal-related code
We're not using proposals at the moment, so it's just in the way.
2020-05-10 10:25:06 +02:00
basti cc2f91afce Do not lint icon templates 2020-05-09 17:39:54 +02:00
basti aa351cbe17 Move icon templates out of pod directory
Glimmer components don't have extra markup around them, so we don't need
the component JS files anymore. Also, we can easily ignore the icon
templates when linting now.
2020-05-09 17:37:58 +02:00
basti 065c635580 Upgrade to latest Ember, etc. 2020-05-09 17:07:35 +02:00
basti 67e3c9a7c3 Merge pull request #185 from 67P/dependabot/npm_and_yarn/jquery-3.5.0
Bump jquery from 3.4.1 to 3.5.0
2020-04-30 19:54:31 +02:00
dependabot[bot] 4f4b3c5c17 Bump jquery from 3.4.1 to 3.5.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.4.1...3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-30 16:18:08 +00:00
basti 4f6bafa31e Merge pull request #184 from 67P/dependabot/npm_and_yarn/acorn-5.7.4
Bump acorn from 5.7.3 to 5.7.4
2020-03-16 10:50:11 -05:00
dependabot[bot] 94846c6e00 Bump acorn from 5.7.3 to 5.7.4
Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4.
- [Release notes](https://github.com/acornjs/acorn/releases)
- [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-16 12:37:30 +00:00
basti 45d553866e Remove obsolete file 2019-12-14 16:35:13 +01:00
basti 8ac6e3ad3c 1.15.0 2019-12-14 12:06:28 +01:00
basti 215ad1d640 Merge pull request #180 from 67P/feature/add_contribution_with_params
Add "give kredits" button to contributor profiles
2019-12-13 17:33:18 +01:00
basti 3f4cc39067 Refactor assignment
Co-authored-by: Garret Alfert <alfert@wevelop.de>
2019-12-13 17:04:52 +01:00
basti 0a6b6180b9 Add "give kredits" button to contributor profiles
closes #172
2019-12-12 17:33:08 +01:00
basti 536868002f Support query params for adding contribution
Just contributor ID, kind, and amount for now.
2019-12-10 18:16:54 +01:00
basti 04a7f9139d Allow partial override of contribution attributes 2019-12-10 13:40:08 +03:00
basti 863b542e3e Add failing test for partial attribute override
Currently the default attributes are only set correctly when either none
or all attributes are handed to the component
2019-12-10 13:18:21 +03:00
basti 23d86f1502 Add unit test for add-contribution component 2019-12-10 13:09:30 +03:00
basti 314cf58c29 1.14.0 2019-12-02 15:59:30 +01:00
basti 9c063b82f1 Merge pull request #169 from 67P/feature/163-small_screen_details_new
Show contributor/contribution details on mobile
2019-12-02 15:57:22 +01:00
basti b397e4faf6 Merge pull request #168 from 67P/bugfix/ios_button_styling
Reset iOS button styling
2019-11-29 21:07:35 +01:00
galfert 256197094e Don't show intro text on details page 2019-11-29 18:06:32 +01:00
galfert 9898d4bb1d Show contributor/contribution details on mobile
Refs #163

On small screens, when selecting a contribution or contributor, the
lists are hidden and only the details are shown.
2019-11-29 18:05:52 +01:00
galfert c321dc51b0 Reset iOS button styling
Removes the pill shaped, shaded button styling on iOS.
2019-11-29 17:44:22 +01:00
105 changed files with 22242 additions and 13444 deletions
-1
View File
@@ -4,7 +4,6 @@
root = true root = true
[*] [*]
end_of_line = lf end_of_line = lf
charset = utf-8 charset = utf-8
+12 -4
View File
@@ -1,8 +1,14 @@
'use strict';
module.exports = { module.exports = {
root: true, root: true,
parser: 'babel-eslint',
parserOptions: { parserOptions: {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module' sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true
}
}, },
globals: { globals: {
console: true console: true
@@ -19,7 +25,10 @@ module.exports = {
}, },
rules: { rules: {
'ember/avoid-leaking-state-in-ember-objects': 'warn', 'ember/avoid-leaking-state-in-ember-objects': 'warn',
'no-console': 'off' 'no-console': 'off',
'ember/no-jquery': 'error',
'ember/require-computed-property-dependencies': 'warn',
'ember/no-observers': 'warn'
}, },
overrides: [ overrides: [
// node files // node files
@@ -35,8 +44,7 @@ module.exports = {
'server/**/*.js' 'server/**/*.js'
], ],
parserOptions: { parserOptions: {
sourceType: 'script', sourceType: 'script'
ecmaVersion: 2015
}, },
env: { env: {
browser: false, browser: false,
+84 -1
View File
@@ -1,10 +1,93 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
extends: 'recommended', extends: 'octane',
rules: { rules: {
'simple-unless': false, 'simple-unless': false,
'no-nested-interactive': false 'no-nested-interactive': false
},
ignore: [
'kredits-web/templates/components/**',
'app/templates/components/**'
],
pending: [
{
"moduleId": "app/templates/dashboard",
"only": [
"no-action"
]
},
{
"moduleId": "app/components/add-contribution/template",
"only": [
"no-action",
"no-curly-component-invocation"
]
},
{
"moduleId": "app/components/add-contributor/template",
"only": [
"no-action",
"no-curly-component-invocation"
]
},
{
"moduleId": "app/components/contribution-list/template",
"only": [
"no-action",
"no-curly-component-invocation"
]
},
{
"moduleId": "app/components/contributor-list/template",
"only": [
"no-action"
]
},
{
"moduleId": "app/components/topbar-account-panel/template",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/contributions/new",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/contributions/resubmit",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/contributors/edit",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/contributors/new",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/signup/eth-account",
"only": [
"no-action"
]
},
{
"moduleId": "app/templates/signup/index",
"only": [
"no-action"
]
} }
]
}; };
+4 -6
View File
@@ -14,16 +14,14 @@ notifications:
addons: addons:
chrome: stable chrome: stable
cache:
directories:
- $HOME/.npm
env: env:
global: global:
# See https://git.io/vdao3 for details. # See https://git.io/vdao3 for details.
- JOBS=1 - JOBS=1
branches:
only:
- master
script: script:
- npm run lint:hbs
- npm run lint:js
- npm test - npm test
+6 -8
View File
@@ -1,14 +1,12 @@
import Application from '@ember/application'; import Application from '@ember/application';
import Resolver from './resolver'; import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers'; import loadInitializers from 'ember-load-initializers';
import config from './config/environment'; import config from './config/environment';
const App = Application.extend({ export default class App extends Application {
modulePrefix: config.modulePrefix, modulePrefix = config.modulePrefix;
podModulePrefix: config.podModulePrefix, podModulePrefix = config.podModulePrefix;
Resolver Resolver = Resolver;
}); }
loadInitializers(App, config.modulePrefix); loadInitializers(App, config.modulePrefix);
export default App;
+7 -7
View File
@@ -1,7 +1,7 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { and, notEmpty } from '@ember/object/computed'; import { and, notEmpty } from '@ember/object/computed';
import { isEmpty } from '@ember/utils'; import { assign } from '@ember/polyfills';
import moment from 'moment'; import moment from 'moment';
export default Component.extend({ export default Component.extend({
@@ -24,10 +24,7 @@ export default Component.extend({
init () { init () {
this._super(...arguments); this._super(...arguments);
this.set('defaultDate', moment().startOf('hour').toDate()); this.set('defaultDate', moment().startOf('hour').toDate());
this.set('defaultAttr', {
// Default attributes used by reset
if (isEmpty(this.attributes)) {
this.set('attributes', {
contributorId: null, contributorId: null,
kind: null, kind: null,
date: this.defaultDate, date: this.defaultDate,
@@ -36,7 +33,8 @@ export default Component.extend({
url: null, url: null,
details: null details: null
}); });
}
this.set('attributes', assign({}, this.defaultAttr, this.attributes));
this.reset(); this.reset();
}, },
@@ -47,7 +45,9 @@ export default Component.extend({
actions: { actions: {
submit () { submit (evt) {
evt.preventDefault();
if (!this.isValid) { if (!this.isValid) {
alert('Invalid data. Please review and try again.'); alert('Invalid data. Please review and try again.');
return; return;
+28 -25
View File
@@ -1,11 +1,11 @@
<form {{action "submit" on="submit"}}> <form onsubmit={{action "submit"}}>
<label> <label>
<p class="label">Contributor:</p> <p class="label">Contributor:</p>
<p> <p>
<select required onchange={{action (mut contributorId) value="target.value"}}> <select required onchange={{action (mut this.contributorId) value="target.value"}}>
<option value="" selected disabled hidden></option> <option value="" selected disabled hidden></option>
{{#each contributors as |contributor|}} {{#each @contributors as |contributor|}}
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option> <option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
{{/each}} {{/each}}
</select> </select>
</p> </p>
@@ -13,14 +13,17 @@
<label> <label>
<p class="label">Kind:</p> <p class="label">Kind:</p>
<p> <p>
<select required onchange={{action (mut kind) value="target.value"}}> <select required onchange={{action (mut this.kind) value="target.value"}}>
<option value="" selected disabled hidden></option> <option value="" selected disabled hidden></option>
<option value="community" selected={{eq kind "community"}}>Community</option> <option value="bureaucracy" selected={{eq this.kind "bureaucracy"}}>Bureaucracy</option>
<option value="design" selected={{eq kind "design"}}>Design</option> <option value="community" selected={{eq this.kind "community"}}>Community</option>
<option value="dev" selected={{eq kind "dev"}}>Development</option> <option value="design" selected={{eq this.kind "design"}}>Design</option>
<option value="docs" selected={{eq kind "docs"}}>Documentation</option> <option value="dev" selected={{eq this.kind "dev"}}>Development</option>
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option> <option value="docs" selected={{eq this.kind "docs"}}>Documentation</option>
<option value="special" selected={{eq kind "special"}}>Special</option> <option value="ops" selected={{eq this.kind "ops"}}>IT Operations</option>
<option value="outreach" selected={{eq this.kind "outreach"}}>Outreach</option>
<option value="qa" selected={{eq this.kind "qa"}}>Quality Assurance</option>
<option value="special" selected={{eq this.kind "special"}}>Special</option>
</select> </select>
</p> </p>
</label> </label>
@@ -28,12 +31,12 @@
<p class="label">Date:</p> <p class="label">Date:</p>
<p> <p>
{{ember-flatpickr {{ember-flatpickr
date=date date=this.date
defaultDate=defaultDate defaultDate=this.defaultDate
maxDate=defaultDate maxDate=this.defaultDate
enableTime=true enableTime=true
time_24hr=true time_24hr=true
onChange=(action (mut date)) onChange=(action (mut this.date))
}} }}
</p> </p>
</label> </label>
@@ -42,33 +45,33 @@
<p> <p>
{{input type="text" {{input type="text"
placeholder="500" placeholder="500"
value=amount value=this.amount
class=(if isValidAmount "valid" "")}} class=(if this.isValidAmount "valid" "")}}
</p> </p>
</label> </label>
<label> <label>
<p class="label">Description:</p> <p class="label">Description:</p>
<p> <p>
{{input type="text" {{input type="text"
value=description value=this.description
class=(if isValidDescription "valid" "")}} class=(if this.isValidDescription "valid" "")}}
</p> </p>
</label> </label>
<label> <label>
<p class="label">URL (optional):</p> <p class="label">URL (optional):</p>
<p> <p>
{{input type="text" {{input type="text"
value=url value=this.url
class=(if isValidUrl "valid" "")}} class=(if this.isValidUrl "valid" "")}}
</p> </p>
</label> </label>
{{#if details}} {{#if this.details}}
<label> <label>
<p class="label">Details:</p> <p class="label">Details:</p>
<p> <p>
<pre> <pre>
{{details}} {{this.details}}
</pre> </pre>
</p> </p>
</label> </label>
@@ -76,8 +79,8 @@
<p class="actions"> <p class="actions">
{{input type="submit" {{input type="submit"
disabled=inProgress disabled=this.inProgress
value=(if inProgress "Processing" "Save")}} value=(if this.inProgress "Processing" "Save")}}
</p> </p>
</form> </form>
+9 -3
View File
@@ -7,8 +7,8 @@ import { isAddress } from 'web3-utils';
export default Component.extend({ export default Component.extend({
kredits: service(),
router: service(), router: service(),
kredits: service(),
attributes: null, attributes: null,
@@ -60,9 +60,15 @@ export default Component.extend({
actions: { actions: {
submit () { submit (evt) {
evt.preventDefault();
if (!this.kredits.currentUserIsCore) {
window.alert('Only core team members can edit profiles. Please ask someone to set you up.');
return;
}
if (!this.isValid) { if (!this.isValid) {
alert('Invalid data. Please review and try again.'); window.alert('Invalid data. Please review and try again.');
return; return;
} }
+24 -23
View File
@@ -1,55 +1,56 @@
<form {{action "submit" on="submit"}}> <form onsubmit={{action "submit"}}>
<p> <p>
<label for="c-account">Ethereum account</label> <label for="c-account">Ethereum account</label>
{{input name="account" id="c-account" <Input @type="text"
type="text" value=account @value={{this.account}}
name="account" id="c-account"
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
class=(if isValidAccount "valid" "")}} class={{if this.isValidAccount "valid" ""}} />
</p> </p>
<p> <p>
<label for="c-kind">Kind</label> <label for="c-kind">Kind</label>
<select required onchange={{action (mut kind) value="target.value"}} id="c-kind"> <select required onchange={{action (mut this.kind) value="target.value"}} id="c-kind">
<option value="person" selected={{eq kind "person"}}>Person</option> <option value="person" selected={{eq this.kind "person"}}>Person</option>
<option value="organization" selected={{eq kind "organization"}}>Organization</option> <option value="organization" selected={{eq this.kind "organization"}}>Organization</option>
</select> </select>
</p> </p>
<p> <p>
<label for="c-name">Name</label> <label for="c-name">Name</label>
{{input name="name" type="text" value=name placeholder="Zero Cool" {{input name="name" type="text" value=this.name placeholder="Zero Cool"
class=(if isValidName "valid" "") id="c-name"}} class=(if this.isValidName "valid" "") id="c-name"}}
</p> </p>
<p> <p>
<label for="c-url">URL</label> <label for="c-url">URL</label>
{{input name="url" type="text" value=url placeholder="http://zerocool.bit" {{input name="url" type="text" value=this.url placeholder="http://zerocool.bit"
class=(if isValidURL "valid" "") id="c-url"}} class=(if this.isValidURL "valid" "") id="c-url"}}
</p> </p>
<p> <p>
<label for="c-github-uid">GitHub UID</label> <label for="c-github-uid">GitHub UID</label>
{{input name="github_uid" type="text" value=github_uid placeholder="2342" {{input name="github_uid" type="text" value=this.github_uid placeholder="2342"
class=(if isValidGithubUID "valid" "") id="c-github-uid"}} class=(if this.isValidGithubUID "valid" "") id="c-github-uid"}}
</p> </p>
<p> <p>
<label for="c-github-username">GitHub username</label> <label for="c-github-username">GitHub username</label>
{{input name="github_username" type="text" value=github_username placeholder="zerocool" {{input name="github_username" type="text" value=this.github_username placeholder="zerocool"
class=(if isValidGithubUsername "valid" "") id="c-github-username"}} class=(if this.isValidGithubUsername "valid" "") id="c-github-username"}}
</p> </p>
<p> <p>
<label for="c-gitea-username">Gitea username</label> <label for="c-gitea-username">Gitea username</label>
{{input name="gitea_username" type="text" value=gitea_username placeholder="zerocool" {{input name="gitea_username" type="text" value=this.gitea_username placeholder="zerocool"
class=(if isValidGiteaUsername "valid" "") id="c-gitea-username"}} class=(if this.isValidGiteaUsername "valid" "") id="c-gitea-username"}}
</p> </p>
<p> <p>
<label for="c-wiki-username">Wiki username</label> <label for="c-wiki-username">Wiki username</label>
{{input name="wiki_username" type="text" value=wiki_username placeholder="ZeroCool" {{input name="wiki_username" type="text" value=this.wiki_username placeholder="ZeroCool"
class=(if isValidWikiUsername "valid" "") id="c-wiki-username"}} class=(if this.isValidWikiUsername "valid" "") id="c-wiki-username"}}
</p> </p>
<p> <p>
<label for="c-zoom-display-name">Zoom display name</label> <label for="c-zoom-display-name">Zoom display name</label>
{{input name="zoom_display_name" type="text" value=zoom_display_name placeholder="Zero Cool" {{input name="zoom_display_name" type="text" value=this.zoom_display_name placeholder="Zero Cool"
class=(if isValidZoomDisplayName "valid" "") id="c-zoom-display-name"}} class=(if this.isValidZoomDisplayName "valid" "") id="c-zoom-display-name"}}
</p> </p>
<p class="actions"> <p class="actions">
{{input type="submit" disabled=inProgress {{input type="submit" disabled=this.inProgress
value=(if inProgress "Processing" "Save")}} value=(if this.inProgress "Processing" "Save")}}
</p> </p>
</form> </form>
-64
View File
@@ -1,64 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { and, notEmpty } from '@ember/object/computed';
export default Component.extend({
attributes: null,
contributors: Object.freeze([]),
isValidContributor: notEmpty('contributorId'),
isValidAmount: computed('amount', function() {
return parseInt(this.amount, 10) > 0;
}),
isValidDescription: notEmpty('description'),
isValidUrl: notEmpty('url'),
isValid: and('isValidContributor',
'isValidAmount',
'isValidDescription'),
init () {
this._super(...arguments);
// Default attributes used by reset
this.set('attributes', {
contributorId: null,
kind: 'community',
amount: null,
description: null,
url: null,
});
},
didInsertElement() {
this._super(...arguments);
this.reset();
},
reset: function() {
this.setProperties(this.attributes);
},
actions: {
submit() {
if (!this.isValid) {
alert('Invalid data. Please review and try again.');
return;
}
let attributes = Object.keys(this.attributes);
let proposal = this.getProperties(attributes);
let saved = this.save(proposal);
// The promise handles inProgress
this.set('inProgress', saved);
saved.then(() => {
this.reset();
window.scroll(0,0);
window.alert('Proposal added.');
});
}
}
});
-44
View File
@@ -1,44 +0,0 @@
<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.github_username}}</option>
{{/each}}
</select>
</p>
<p>
<select required onchange={{action (mut kind) value="target.value"}}>
<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>
</select>
</p>
<p>
{{input type="text"
placeholder="100"
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>
<p class="actions">
{{input type="submit"
disabled=(is-pending inProgress)
value=(if (is-pending inProgress) "Processing" "Save")}}
{{#link-to "index"}}Back{{/link-to}}
</p>
</form>
@@ -1,7 +1,6 @@
<div class="chart"> <div class="chart">
{{ember-chart type="doughnut" <EmberChart @type="doughnut"
data=chartData @data={{this.chartData}}
options=chartOptions @options={{this.chartOptions}}
width=200 @width="200" @height="200" />
height=200}}
</div> </div>
@@ -24,12 +24,10 @@ export default Component.extend({
contributors: sort('kredits.contributors', 'contributorsSorting'), contributors: sort('kredits.contributors', 'contributorsSorting'),
contributorsActive: computed('contributors.[]', 'contributions', function() { contributorsActive: computed('contributors.[]', 'contributions', function() {
let activeIds = this.contributions.mapBy('contributorId') const activeIds = new Set(this.contributions.mapBy('contributorId')
.map(id => id.toString()) .map(id => id.toString()));
.uniq();
return this.contributors.filter(c => { return this.contributors.filter(c => activeIds.has(c.id.toString()));
return activeIds.includes(c.id.toString());
});
}), }),
contributionKinds: computed('contributions.[]', function() { contributionKinds: computed('contributions.[]', function() {
+16 -17
View File
@@ -1,41 +1,41 @@
{{#if showQuickFilter}} {{#if @showQuickFilter}}
<div class="quick-filter"> <div class="quick-filter">
<p> <p>
<label class="filter-contributor"> <label class="filter-contributor">
Contributor: Contributor:
<select onchange={{action (mut contributorId) value="target.value"}}> <select onchange={{action (mut this.contributorId) value="target.value"}}>
<option value="" selected>all</option> <option value="" selected>all</option>
{{#each contributorsActive as |contributor|}} {{#each this.contributorsActive as |contributor|}}
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option> <option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
{{/each}} {{/each}}
</select> </select>
</label> </label>
<label class="filter-contribution-kind"> <label class="filter-contribution-kind">
Kind: Kind:
<select onchange={{action (mut contributionKind) value="target.value"}}> <select onchange={{action (mut this.contributionKind) value="target.value"}}>
<option value="" selected>all</option> <option value="" selected>all</option>
{{#each contributionKinds as |kind|}} {{#each this.contributionKinds as |kind|}}
<option value={{kind}} selected={{eq contributionKind kind}}>{{capitalize-string kind}}</option> <option value={{kind}} selected={{eq this.contributionKind kind}}>{{capitalize-string kind}}</option>
{{/each}} {{/each}}
</select> </select>
</label> </label>
<label class="filter-contribution-size"> <label class="filter-contribution-size">
{{input type="checkbox" checked=hideSmallContributions}} <Input @type="checkbox" @checked={{this.hideSmallContributions}} />
Hide small contributions Hide small contributions
</label> </label>
</p> </p>
</div> </div>
{{/if}} {{/if}}
<ul class="contribution-list"> <ul class="contribution-list {{if @loading 'loading'}}">
{{#each contributionsFiltered as |contribution|}} {{#each this.contributionsFiltered as |contribution|}}
<li role="button" {{action "openContributionDetails" contribution}} <li role="button" data-contribution-id={{contribution.id}}
data-contribution-id={{contribution.id}} {{action "openContributionDetails" contribution}}
class="{{contribution-status contribution}}{{if (eq contribution.id selectedContributionId) " selected"}}"> class="{{contribution-status contribution}}{{if (eq contribution.id @selectedContributionId) " selected"}}">
<p class="meta"> <p class="meta">
<span class="recipient">{{user-avatar contributor=contribution.contributor}}</span> <span class="recipient"><UserAvatar @contributor={{contribution.contributor}} /></span>
<span class="category {{contribution.kind}}">({{contribution.kind}})</span> <span class="category {{contribution.kind}}">({{contribution.kind}})</span>
<span class="title">{{contribution.description}}</span> <span class="title">{{contribution.description}}</span>
</p> </p>
@@ -45,10 +45,9 @@
{{#unless contribution.vetoed}} {{#unless contribution.vetoed}}
{{#unless (is-confirmed-contribution contribution)}} {{#unless (is-confirmed-contribution contribution)}}
<p class="voting"> <p class="voting">
{{input type="button" class="button small danger" {{input type="button" class="button small danger" value="veto"
click=(action "veto" contribution.id) click=(action "veto" contribution.id)
disabled=contribution.hasPendingChanges disabled=contribution.hasPendingChanges}}
value="veto"}}
</p> </p>
{{/unless}} {{/unless}}
{{/unless}} {{/unless}}
+1 -4
View File
@@ -2,12 +2,10 @@ import Component from '@ember/component';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
export default Component.extend({ export default Component.extend({
tagName: '',
router: service(), router: service(),
tagName: 'table',
classNames: 'contributor-list',
selectedContributorId: null, selectedContributorId: null,
actions: { actions: {
@@ -17,5 +15,4 @@ export default Component.extend({
} }
} }
}); });
+10 -5
View File
@@ -1,13 +1,17 @@
<table class="contributor-list {{if @loading 'loading'}}">
<thead>
</thead>
<tbody> <tbody>
{{#each contributorList as |c|}} {{#each @contributorList as |c|}}
<tr role="button" {{action "openContributorDetails" c.contributor}} <tr role="button"
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}"> onclick={{action "openContributorDetails" c.contributor}}
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id @selectedContributorId) "selected"}}">
<td class="person"> <td class="person">
{{user-avatar contributor=c.contributor}} {{c.contributor.name}} <UserAvatar @contributor={{c.contributor}} /> {{c.contributor.name}}
</td> </td>
<td class="kredits"> <td class="kredits">
<span class="amount"> <span class="amount">
{{#if showUnconfirmedKredits}} {{#if @showUnconfirmedKredits}}
{{c.amountTotal}} {{c.amountTotal}}
{{else}} {{else}}
{{c.amountConfirmed}} {{c.amountConfirmed}}
@@ -18,3 +22,4 @@
</tr> </tr>
{{/each}} {{/each}}
</tbody> </tbody>
</table>
@@ -1,4 +1,5 @@
<a href={{account.url}} target="_blank" rel="noopener" title="{{account.username}} on {{account.site}}"> <a href={{this.account.url}} target="_blank" rel="noopener noreferrer"
<span class="site">{{account.site}}</span> title="{{this.account.username}} on {{this.account.site}}">
{{component iconComponentName}} <span class="site">{{this.account.site}}</span>
{{component this.iconComponentName}}
</a> </a>
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
-5
View File
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
@@ -1,5 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: ""
});
-20
View File
@@ -1,20 +0,0 @@
import Component from '@ember/component';
export default Component.extend({
tagName: 'ul',
classNames: ['proposal-list'],
actions: {
confirm(proposalId) {
if (this.contractInteractionEnabled) {
this.confirmProposal(proposalId);
} else {
window.alert('Only members can vote on proposals. Please ask someone to set you up.');
}
}
}
});
-20
View File
@@ -1,20 +0,0 @@
{{#each proposals as |proposal|}}
<li data-proposal-id={{proposal.id}} class={{if proposal.isExecuted "confirmed" "unconfirmed"}}>
<p class="meta">
<span class="category {{proposal.kind}}">♥ ({{proposal.kind}})</span>
<span class="recipient">{{proposal.contributor.name}}:</span>
</p>
<p class="kredits-amount">
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
</p>
<p class="description">
<span class="description">{{proposal.description}}</span>
</p>
<p class="voting">
{{#unless proposal.isExecuted}}
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
<button {{action "confirm" proposal.id}}>+1</button>
{{/unless}}
</p>
</li>
{{/each}}
@@ -1,17 +1,17 @@
<section id="user-account"> <section id="user-account">
{{#if setupInProgress}} {{#if this.setupInProgress}}
Connecting account... Connecting account...
{{else}} {{else}}
{{#if (and kredits.hasAccounts kredits.currentUser)}} {{#if (and this.kredits.hasAccounts this.kredits.currentUser)}}
{{kredits.currentUser.name}} {{this.kredits.currentUser.name}}
{{#if kredits.currentUserIsCore}} {{#if this.kredits.currentUserIsCore}}
<span class="core-flag">(core)</span> <span class="core-flag">(core)</span>
{{/if}} {{/if}}
{{else}} {{else}}
<span class="anonymous">Anonymous</span> <span class="anonymous">Anonymous</span>
<button {{action "signup"}} class="small">Sign up</button> <button onclick={{action "signup"}} class="small" type="button">Sign up</button>
{{#if showConnectButton}} {{#if this.showConnectButton}}
<button {{action "connectAccount"}} class="small green">Connect account</button> <button onclick={{action "connectAccount"}} class="small green" type="button">Connect account</button>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/if}} {{/if}}
+3
View File
@@ -24,6 +24,9 @@ export default Component.extend({
if (github_uid) { if (github_uid) {
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`; return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`;
} else {
// TODO use custom avatar
return '';
} }
}) })
+2
View File
@@ -6,6 +6,8 @@ export default Controller.extend({
kredits: service(), kredits: service(),
queryParams: ['contributorId', 'kind', 'amount'],
contributors: alias('kredits.contributors'), contributors: alias('kredits.contributors'),
minedContributors: filterBy('contributors', 'id'), minedContributors: filterBy('contributors', 'id'),
-6
View File
@@ -45,12 +45,6 @@ export default Controller.extend({
}); });
}, },
confirmProposal (proposalId) {
this.kredits.vote(proposalId).then(transaction => {
console.debug('[controllers:index] Vote submitted to Ethereum blockhain: '+transaction.hash);
});
},
toggleQuickFilterUnconfirmed () { toggleQuickFilterUnconfirmed () {
this.toggleProperty('showQuickFilterUnconfirmed'); this.toggleProperty('showQuickFilterUnconfirmed');
}, },
-24
View File
@@ -1,24 +0,0 @@
import Controller from '@ember/controller';
import { alias, filterBy } from '@ember/object/computed';
import { inject as service } from '@ember/service';
export default Controller.extend({
kredits: service(),
contributors: alias('kredits.contributors'),
minedContributors: filterBy('contributors', 'id'),
actions: {
save(proposal) {
// contributorIpfsHash is needed for the proposal ipfs data. I'm not happy to do this here but I think to load all the contributors in addProposal again is a bit too much. I hope we can refactor it later.
let contributor = this.contributors.findBy('id', proposal.contributorId);
proposal.contributorIpfsHash = contributor.get('ipfsHash');
return this.kredits.addProposal(proposal)
.then((proposal) => {
this.transitionToRoute('index');
return proposal;
});
}
}
});
+2 -4
View File
@@ -45,14 +45,12 @@ export default Helper.extend({
contribution.removeObserver('vetoed', this, this.triggerRecompute); contribution.removeObserver('vetoed', this, this.triggerRecompute);
contribution.removeObserver('confirmedAt', this, this.triggerRecompute); contribution.removeObserver('confirmedAt', this, this.triggerRecompute);
contribution.removeObserver('currentBlock', this, this.triggerRecompute); contribution.removeObserver('currentBlock', this, this.triggerRecompute);
contribution.removeObserver('hadPendingChanges', this, this.triggerRecompute); contribution.removeObserver('hasPendingChanges', this, this.triggerRecompute);
}; };
}, },
triggerRecompute () { triggerRecompute () {
once(this, function () { once(this, this.recompute);
this.recompute();
});
} }
}); });
+11 -5
View File
@@ -1,6 +1,5 @@
import EmberObject, { computed } from '@ember/object'; import EmberObject, { computed } from '@ember/object';
import { isEmpty, isPresent } from '@ember/utils'; import { isEmpty, isPresent } from '@ember/utils';
import bignumber from 'kredits-web/utils/cps/bignumber';
import moment from 'moment'; import moment from 'moment';
export default EmberObject.extend({ export default EmberObject.extend({
@@ -9,11 +8,15 @@ export default EmberObject.extend({
id: null, id: null,
contributorId: null, contributorId: null,
amount: null, amount: null,
confirmedAt: bignumber('confirmedAtBlock', 'toNumber'), confirmedAt: null,
vetoed: null, vetoed: null,
ipfsHash: null, ipfsHash: null,
creatorAccount: null, // contributor model instance
contributor: null,
// TODO contributor who submitted the contribution
// submittedBy: null,
// IPFS // IPFS
kind: null, kind: null,
@@ -22,7 +25,6 @@ export default EmberObject.extend({
url: null, url: null,
date: null, date: null,
time: null, time: null,
ipfsData: '',
pendingTx: null, pendingTx: null,
@@ -41,6 +43,10 @@ export default EmberObject.extend({
hasPendingChanges: computed('pendingTx', function() { hasPendingChanges: computed('pendingTx', function() {
return isPresent(this.pendingTx); return isPresent(this.pendingTx);
}) }),
serialize () {
return JSON.stringify(this);
}
}); });
+8 -7
View File
@@ -1,14 +1,12 @@
import EmberObject from '@ember/object'; import EmberObject from '@ember/object';
import bignumber from 'kredits-web/utils/cps/bignumber';
import kreditsValue from 'kredits-web/utils/cps/kredits';
export default EmberObject.extend({ export default EmberObject.extend({
// Contract // Contract
id: bignumber('idRaw', 'toString'), id: null,
account: null, account: null,
balance: kreditsValue('balanceRaw'), balance: 0,
totalKreditsEarned: bignumber('totalKreditsEarnedRaw', 'toNumber'), totalKreditsEarned: 0,
contributionsCount: bignumber('contributionsCountRaw', 'toNumber'), contributionsCount: 0,
isCore: false, isCore: false,
ipfsHash: null, ipfsHash: null,
@@ -20,6 +18,9 @@ export default EmberObject.extend({
github_uid: null, github_uid: null,
wiki_username: null, wiki_username: null,
zoom_display_name: null, zoom_display_name: null,
ipfsData: ''
serialize () {
return JSON.stringify(this);
}
}); });
-32
View File
@@ -1,32 +0,0 @@
import EmberObject from '@ember/object';
import { alias } from '@ember/object/computed';
import bignumber from 'kredits-web/utils/cps/bignumber';
export default EmberObject.extend({
// Contract
id: bignumber('idRaw', 'toString'),
creatorAccount: null,
contributorId: bignumber('contributorIdRaw', 'toString'),
amount: bignumber('amountRaw', 'toNumber'),
votesCount: bignumber('votesCountRaw', 'toNumber'),
votesNeeded: bignumber('votesNeededRaw', 'toNumber'),
executed: null,
ipfsHash: null,
// Shortcuts
isExecuted: alias('executed'),
// IPFS
kind: null,
description: null,
details: null,
url: null,
ipfsData: '',
init () {
this._super(...arguments);
this.set('details', {});
}
});
-3
View File
@@ -1,3 +0,0 @@
import Resolver from 'ember-resolver';
export default Resolver;
+5 -10
View File
@@ -1,10 +1,10 @@
import EmberRouter from '@ember/routing/router'; import EmberRouter from '@ember/routing/router';
import config from './config/environment'; import config from './config/environment';
const Router = EmberRouter.extend({ export default class Router extends EmberRouter {
location: config.locationType, location = config.locationType;
rootURL: config.rootURL rootURL = config.rootURL;
}); }
Router.map(function() { Router.map(function() {
this.route('dashboard', function() { this.route('dashboard', function() {
@@ -16,11 +16,8 @@ Router.map(function() {
this.route('show', { path: ':id' }); this.route('show', { path: ':id' });
}); });
}); });
this.route('proposals', function() {
this.route('new');
});
this.route('contributions', function() { this.route('contributions', function() {
this.route('new'); this.route('new', { queryParams: ['contributorId', 'kind', 'amount'] });
this.route('resubmit', { path: ':id/resubmit' }); this.route('resubmit', { path: ':id/resubmit' });
}); });
this.route('contributors', function() { this.route('contributors', function() {
@@ -33,5 +30,3 @@ Router.map(function() {
this.route('complete'); this.route('complete');
}); });
}); });
export default Router;
+13
View File
@@ -1,5 +1,6 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { schedule } from '@ember/runloop';
export default Route.extend({ export default Route.extend({
kredits: service(), kredits: service(),
@@ -21,6 +22,18 @@ export default Route.extend({
return this.kredits.loadInitialData() return this.kredits.loadInitialData()
.then(() => { .then(() => {
this.kredits.addContractEventHandlers(); this.kredits.addContractEventHandlers();
})
.then(() => {
if (this.kredits.contributorsNeedSync) {
schedule('afterRender', this.kredits.syncContributors,
this.kredits.syncContributors.perform);
}
if (this.kredits.contributionsNeedSync) {
schedule('afterRender', this.kredits.syncContributions,
this.kredits.syncContributions.perform);
}
schedule('afterRender', this.kredits.fetchMissingContributions,
this.kredits.fetchMissingContributions.perform);
}); });
} }
}); });
+9
View File
@@ -0,0 +1,9 @@
import Route from '@ember/routing/route';
export default Route.extend({
model (params) {
return { params };
}
});
+27
View File
@@ -0,0 +1,27 @@
import Service from '@ember/service';
import * as localforage from 'localforage';
import config from 'kredits-web/config/environment';
function createStore(name) {
const networkName = config.web3RequiredNetwork || 'custom';
return localforage.createInstance({ name: `kredits:${networkName}:${name}` });
}
export default class BrowserCacheService extends Service {
constructor() {
super(...arguments);
this.stores = {
contributors: createStore('contributors'),
contributions: createStore('contributions')
}
}
get contributors() {
return this.stores.contributors;
}
get contributions() {
return this.stores.contributions;
}
}
+170 -101
View File
@@ -1,35 +1,43 @@
import ethers from 'ethers'; import ethers from 'ethers';
import Kredits from 'kredits-contracts'; import Kredits from 'kredits-contracts';
import RSVP from 'rsvp';
import Service from '@ember/service'; import Service from '@ember/service';
import EmberObject from '@ember/object'; import EmberObject from '@ember/object';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { alias, notEmpty } from '@ember/object/computed'; import { alias, notEmpty } from '@ember/object/computed';
import { isEmpty, isPresent } from '@ember/utils'; import { isEmpty, isPresent } from '@ember/utils';
import { inject as service } from '@ember/service';
import { task, taskGroup } from 'ember-concurrency';
import groupBy from 'kredits-web/utils/group-by'; import groupBy from 'kredits-web/utils/group-by';
import processContributorData from 'kredits-web/utils/process-contributor-data';
import processContributionData from 'kredits-web/utils/process-contribution-data';
import formatKredits from 'kredits-web/utils/format-kredits'; import formatKredits from 'kredits-web/utils/format-kredits';
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 Contribution from 'kredits-web/models/contribution' import Contribution from 'kredits-web/models/contribution'
export default Service.extend({ export default Service.extend({
browserCache: service(),
currentBlock: null, currentBlock: null,
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded. currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
currentUser: null, currentUser: null,
contributors: null, contributors: null,
contributions: null, contributions: null,
proposals: null,
githubAccessToken: null, githubAccessToken: null,
currentUserIsContributor: notEmpty('currentUser'), currentUserIsContributor: notEmpty('currentUser'),
currentUserIsCore: alias('currentUser.isCore'), currentUserIsCore: alias('currentUser.isCore'),
hasAccounts: notEmpty('currentUserAccounts'), hasAccounts: notEmpty('currentUserAccounts'),
// When data was loaded from cache, we need to fetch updates from the network
contributorsNeedSync: false,
contributionsNeedSync: false,
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() { contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
return this.contributions.filter(contribution => { return this.contributions.filter(contribution => {
return contribution.confirmedAt > this.currentBlock; return contribution.confirmedAt > this.currentBlock;
@@ -77,7 +85,6 @@ export default Service.extend({
init () { init () {
this._super(...arguments); this._super(...arguments);
this.set('contributors', []); this.set('contributors', []);
this.set('proposals', []);
this.set('contributions', []); this.set('contributions', []);
}, },
@@ -87,7 +94,7 @@ export default Service.extend({
getEthProvider () { getEthProvider () {
let ethProvider; let ethProvider;
return new RSVP.Promise(async (resolve) => { return new Promise(resolve => {
function instantiateWithoutAccount () { function instantiateWithoutAccount () {
console.debug('[kredits] Creating new instance from npm module class'); console.debug('[kredits] Creating new instance from npm module class');
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`); console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
@@ -172,12 +179,24 @@ export default Service.extend({
.then(total => total.toNumber()); .then(total => total.toNumber());
}), }),
async loadInitialData () {
const numCachedContributors = await this.browserCache.contributors.length();
if (numCachedContributors > 0) {
await this.loadContributorsFromCache();
this.set('contributorsNeedSync', true);
} else {
await this.fetchContributors();
}
loadInitialData () { const numCachedContributions = await this.browserCache.contributions.length();
return this.getContributors() if (numCachedContributions > 0) {
.then(contributors => this.contributors.pushObjects(contributors)) await this.loadContributionsFromCache();
.then(() => this.getContributions()) this.set('contributionsNeedSync', true);
.then(contributions => this.contributions.pushObjects(contributions)) } else {
await this.fetchContributions({ page: { size: 30 } });
}
return Promise.resolve();
}, },
addContributor (attributes) { addContributor (attributes) {
@@ -208,15 +227,48 @@ export default Service.extend({
}); });
}, },
getContributors () { fetchContributors () {
console.debug(`[kredits] Fetching all contributors from the network`);
return this.kredits.Contributor.all() return this.kredits.Contributor.all()
.then(contributors => { .then(contributors => {
return contributors.map(contributor => { return contributors.forEach(data => {
return Contributor.create(contributor); this.loadContributorFromData(data);
return;
}); });
})
.then(() => {
return this.cacheLoadedContributors();
}); });
}, },
loadContributorFromData(data) {
const contributor = Contributor.create(processContributorData(data));
const loadedContributor = this.contributors.findBy('id', contributor.id);
if (loadedContributor) { this.contributors.removeObject(loadedContributor); }
this.contributors.pushObject(contributor);
return contributor;
},
async cacheLoadedContributors () {
for (const c of this.contributors) {
await this.browserCache.contributors.setItem(c.id, c.serialize());
}
console.debug(`[kredits] Cached ${this.contributors.length} contributors in browser storage`);
return Promise.resolve();
},
async loadContributorsFromCache () {
return this.browserCache.contributors.iterate((value/*, key , iterationNumber */) => {
this.contributors.pushObject(Contributor.create(JSON.parse(value)));
}).then((/* result */) => {
console.debug(`[kredits] Loaded ${this.contributors.length} contributors from cache`);
});
},
syncContributors: task(function * () {
yield this.fetchContributors();
}),
addContribution (attributes) { addContribution (attributes) {
console.debug('[kredits] add contribution', attributes); console.debug('[kredits] add contribution', attributes);
@@ -232,51 +284,116 @@ export default Service.extend({
}); });
}, },
// fetchContributions (options = { page: { size: 200 } }) {
// TODO Implement proposals with voting console.debug(`[kredits] Fetching contributions from the network`);
// return this.kredits.Contribution.all(options)
// 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);
// });
// });
// },
getContributions () {
return this.kredits.Contribution.all({page: {size: 200}})
.then(contributions => { .then(contributions => {
return contributions.map(contribution => { return contributions.map(data => {
contribution.contributor = this.contributors.findBy('id', contribution.contributorId.toString()); const contribution = this.loadContributionFromData(data);
return Contribution.create(contribution); return contribution;
});
})
.then(contributions => {
const cacheWrites = contributions.map(c => {
return this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
});
return Promise.all(cacheWrites).then(() => {
console.debug(`[kredits] Cached ${contributions.length} contributions in browser storage`);
}); });
}); });
}, },
vote (proposalId) { loadContributionFromData(data) {
console.debug('[kredits] vote for', proposalId); const contribution = Contribution.create(processContributionData(data));
contribution.set('contributor', this.contributors.findBy('id', data.contributorId.toString()));
const loadedContribution = this.contributions.findBy('id', contribution.id);
if (loadedContribution) { this.contributions.removeObject(loadedContribution); }
this.contributions.pushObject(contribution);
return contribution;
},
return this.kredits.Proposal.functions.vote(proposalId) async cacheLoadedContributions () {
.then(data => { for (const c of this.contributions) {
console.debug('[kredits] vote response', data); await this.browserCache.contributions.setItem(c.id, c.serialize());
return data; }
console.debug(`[kredits] Cached ${this.contributions.length} contributions in browser storage`);
return Promise.resolve();
},
async loadContributionsFromCache () {
return this.browserCache.contributions.iterate((value/*, key , iterationNumber */) => {
this.contributions.pushObject(Contribution.create(JSON.parse(value)));
}).then((/* result */) => {
console.debug(`[kredits] Loaded ${this.contributions.length} contributions from cache`);
}); });
}, },
contributionTasks: taskGroup().enqueue(),
syncContributions: task(function * () {
yield this.fetchNewContributions.perform();
yield this.syncUnconfirmedContributions.perform();
}).group('contributionTasks'),
fetchNewContributions: task(function * () {
const count = yield this.kredits.Contribution.functions.contributionsCount();
const lastKnownContributionId = Math.max.apply(null, this.contributions.mapBy('id'));
const toFetch = count - lastKnownContributionId;
if (toFetch > 0) {
console.debug(`[kredits] Fetching ${toFetch} new contributions`);
for (let id = lastKnownContributionId; id <= count; id++) {
const data = yield this.kredits.Contribution.getById(id);
const c = this.loadContributionFromData(data);
yield this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
}
} else {
console.debug(`[kredits] No new contributions to fetch`);
}
}),
fetchMissingContributions: task(function * () {
const count = yield this.kredits.Contribution.functions.contributionsCount();
const allIds = [...Array(count+1).keys()];
allIds.shift(); // remove first item, which is 0
const loadedContributions = new Set(this.contributions.mapBy('id'));
const toFetch = allIds.filter(id => !loadedContributions.has(id));
if (toFetch.length === 0) {
console.debug(`[kredits] No contributions left to fetch`);
return;
}
console.debug(`[kredits] Fetching ${toFetch.length} past contributions`);
let countFetched = 0;
for (let id = count; id > 0; id--) {
if (loadedContributions.has(id)) {
continue;
} else {
const data = yield this.kredits.Contribution.getById(id);
const c = this.loadContributionFromData(data);
yield this.browserCache.contributions.setItem(c.id.toString(), c.serialize());
countFetched++;
if (countFetched % 20 === 0) {
console.debug(`[kredits] Fetched ${countFetched} more contributions`);
}
}
}
console.debug(`[kredits] Cached ${countFetched} past contributions`);
}).group('contributionTasks'),
syncUnconfirmedContributions: task(function * () {
if (this.contributionsUnconfirmed.length > 0) {
console.debug(`[kredits] Syncing unconfirmed contributions`);
for (const c of this.contributionsUnconfirmed) {
const data = yield this.kredits.Contribution.getById(c.id);
const contribution = this.loadContributionFromData(data);
yield this.browserCache.contributions.setItem(c.id.toString(), contribution.serialize());
}
} else {
console.debug(`[kredits] No unconfirmed contributions to sync`);
}
}),
veto (contributionId) { veto (contributionId) {
console.debug('[kredits] veto against', contributionId); console.debug('[kredits] veto against', contributionId);
const contribution = this.contributions.findBy('id', contributionId); const contribution = this.contributions.findBy('id', contributionId);
@@ -289,26 +406,22 @@ export default Service.extend({
}); });
}, },
getCurrentUser: computed('kredits.provider', function() { getCurrentUser: computed('kredits.provider', 'currentUserAccounts.[]', function() {
if (isEmpty(this.currentUserAccounts)) { if (isEmpty(this.currentUserAccounts)) {
return RSVP.resolve(); return Promise.resolve();
} }
return this.kredits.Contributor return this.kredits.Contributor
.functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject')) .functions.getContributorIdByAddress(this.currentUserAccounts.firstObject)
.then((id) => { .then((id) => {
// check if the user is a contributor or not // check if the user is a contributor or not
if (id === 0) { if (id === 0) {
return RSVP.resolve(); return Promise.resolve();
} else { } else {
return this.kredits.Contributor.getById(id); return this.kredits.Contributor.getById(id);
} }
}); });
}), }),
findProposalById(proposalId) {
return this.proposals.findBy('id', proposalId.toString());
},
// Contract events // Contract events
addContractEventHandlers () { addContractEventHandlers () {
this.kredits.Contributor this.kredits.Contributor
@@ -320,11 +433,6 @@ export default Service.extend({
.on('ContributionAdded', this.handleContributionAdded.bind(this)) .on('ContributionAdded', this.handleContributionAdded.bind(this))
.on('ContributionVetoed', this.handleContributionVetoed.bind(this)) .on('ContributionVetoed', this.handleContributionVetoed.bind(this))
this.kredits.Proposal
.on('ProposalCreated', this.handleProposalCreated.bind(this))
.on('ProposalVoted', this.handleProposalVoted.bind(this))
.on('ProposalExecuted', this.handleProposalExecuted.bind(this));
this.kredits.Token this.kredits.Token
.on('Transfer', this.handleTransfer.bind(this)); .on('Transfer', this.handleTransfer.bind(this));
}, },
@@ -374,45 +482,6 @@ export default Service.extend({
} }
}, },
handleProposalCreated (proposalId) {
let proposal = this.findProposalById(proposalId);
if (proposal) {
console.debug('[events] proposal exists, not adding from event');
return;
}
this.kredits.Proposal.getById(proposalId)
.then((proposal) => {
proposal.contributor = this.contributors.findBy('id', proposal.contributorId.toString());
this.proposals.pushObject(Proposal.create(proposal));
});
},
// TODO: We may want to reload that proposal to show the voter as voted
handleProposalVoted (proposalId, voterId, totalVotes) {
let proposal = this.findProposalById(proposalId);
if (proposal) {
proposal.set('votesCount', totalVotes);
}
},
handleProposalExecuted (proposalId, contributorId, amount) {
let proposal = this.findProposalById(proposalId);
if (proposal.get('isExecuted')) {
console.debug('[events] proposal already executed, not adding from event');
return;
}
proposal.set('executed', true);
this.contributors
.findBy('id', contributorId.toString())
.incrementProperty('balance', amount);
},
handleTransfer (from, to, value) { handleTransfer (from, to, value) {
value = value.toNumber(); value = value.toNumber();
+1
View File
@@ -10,6 +10,7 @@ button, input[type=submit], .button {
text-transform: uppercase; text-transform: uppercase;
cursor: pointer; cursor: pointer;
letter-spacing: 0.1em; letter-spacing: 0.1em;
-webkit-appearance: none;
&+button, &+input[type=submit], &+.button { &+button, &+input[type=submit], &+.button {
margin-left: 0.5rem; margin-left: 0.5rem;
-1
View File
@@ -1,6 +1,5 @@
section#add-contributor, section#add-contributor,
section#add-contribution, section#add-contribution,
section#add-proposal,
section#signup { section#signup {
form { form {
+17
View File
@@ -0,0 +1,17 @@
@mixin loading-border-top {
&::before {
display: block;
width: 100%;
height: 1px;
content: '';
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.2) 40%, #68d7fb 60%, rgba(255, 255, 255, 0.2));
background-size: 200% 200%;
animation: kitt 2.5s linear infinite;
}
}
@keyframes kitt {
0%{ background-position: 0% 0%}
50%{ background-position: 100% 0%}
100%{ background-position: 0% 0%}
}
+1 -17
View File
@@ -76,22 +76,6 @@ section {
} }
} }
} }
&#proposals-open, &#proposals-closed {
.actions {
padding-top: 3rem;
font-size: 1rem;
color: $primary-color;
text-align: center;
@include media-max(small) {
padding-top: 2rem;
}
a {
color: $primary-color;
}
}
}
} }
@media (min-width: 550px) { @media (min-width: 550px) {
@@ -104,6 +88,7 @@ section {
@import "buttons"; @import "buttons";
@import "forms"; @import "forms";
@import "sugar";
@import "components/contribution-list"; @import "components/contribution-list";
@import "components/contribution-details"; @import "components/contribution-details";
@@ -111,7 +96,6 @@ section {
@import "components/contributor-profile"; @import "components/contributor-profile";
@import "components/external-account-link"; @import "components/external-account-link";
@import "components/loading-spinner"; @import "components/loading-spinner";
@import "components/proposal-list";
@import "components/topbar"; @import "components/topbar";
@import "components/topbar-account-panel"; @import "components/topbar-account-panel";
@import "components/user-avatar"; @import "components/user-avatar";
@@ -84,5 +84,17 @@ section#contribution-details {
} }
} }
} }
}
// On small screens, hide intro text, contributor and contributions list when
// showing details
@include media-max(small) {
#dashboard.with-details {
#contributions, #stats {
display: none;
}
}
#intro.with-details {
display: none;
}
} }
+4 -29
View File
@@ -114,37 +114,12 @@ ul.contribution-list {
} }
} }
} &.loading {
@include loading-border-top;
@media (min-width: 550px) {
ul.proposal-list {
li { li {
grid-template-columns: auto 10rem; &:first-of-type {
grid-row-gap: 0.5rem; border-top: none;
p {
&.kredits-amount, &.voting {
text-align: right;
}
}
&.unconfirmed {
p {
&.kredits-amount, &.voting {
text-align: right;
}
&.description {
grid-column-start: span 1;
}
&.voting {
grid-column-start: span 1;
}
}
}
.description {
} }
} }
} }
@@ -1,4 +1,5 @@
table.contributor-list { table.contributor-list {
position: relative;
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
@@ -48,4 +49,11 @@ table.contributor-list {
} }
} }
} }
&.loading {
@include loading-border-top;
&::before {
position: absolute;
}
}
} }
@@ -42,6 +42,10 @@ section#contributor-profile {
} }
} }
} }
.actions {
margin-top: 2rem;
}
} }
.actions { .actions {
-111
View File
@@ -1,111 +0,0 @@
ul.proposal-list {
clear: both;
width: 100%;
list-style: none;
li {
display: grid;
grid-template-columns: auto 5rem;
grid-row-gap: 0.5rem;
padding: 1rem 1.2rem;
background-color: rgba(255,255,255,0.1);
font-size: 1.2rem;
border-bottom: 1px solid rgba(255,255,255,0.2);
&:first-of-type {
border-top: 1px solid rgba(255,255,255,0.2);
}
p {
align-self: center;
margin: 0;
line-height: 2rem;
&.kredits-amount, &.voting {
text-align: right;
}
&.description {
grid-column-start: span 2;
}
&.voting {
grid-column-start: span 2;
}
}
span {
}
.description {
line-height: 1.4em;
font-size: 1rem;
}
.category {
color: $blue;
padding-right: 0.2rem;
&.community { color: $red; }
&.dev { color: $pink; }
&.design { color: $yellow; }
&.docs { color: $green; }
&.ops { color: $purple; }
}
.amount {
font-weight: 500;
}
.symbol {
font-size: 1rem;
font-weight: 500;
padding-left: 0.2rem;
}
.recipient {
font-weight: 500;
}
.votes {
font-size: 1rem;
color: $primary-color;
margin-right: 0.5rem;
}
}
}
@media (min-width: 550px) {
ul.proposal-list {
li {
grid-template-columns: auto 10rem;
grid-row-gap: 0.5rem;
p {
&.kredits-amount, &.voting {
text-align: right;
}
}
&.unconfirmed {
p {
&.kredits-amount, &.voting {
text-align: right;
}
&.description {
grid-column-start: span 1;
}
&.voting {
grid-column-start: span 1;
}
}
}
.description {
}
}
}
}
+1 -1
View File
@@ -1 +1 @@
{{loading-spinner}} <LoadingSpinner />
+2 -2
View File
@@ -1,6 +1,6 @@
<header id="topbar"> <header id="topbar">
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1> <h1><LinkTo @route="dashboard">Kosmos Kredits</LinkTo></h1>
{{topbar-account-panel}} <TopbarAccountPanel />
</header> </header>
{{outlet}} {{outlet}}
View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before

Width:  |  Height:  |  Size: 703 B

After

Width:  |  Height:  |  Size: 703 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 750 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

+3 -1
View File
@@ -6,7 +6,9 @@
</header> </header>
<div class="content"> <div class="content">
{{add-contribution contributors=sortedContributors save=(action "save")}} <AddContribution @contributors={{this.sortedContributors}}
@attributes={{this.model.params}}
@save={{action "save"}} />
</div> </div>
</section> </section>
+4 -2
View File
@@ -2,11 +2,13 @@
<section id="add-contribution"> <section id="add-contribution">
<header> <header>
<h2>Re-submit contribution #{{model.id}}</h2> <h2>Re-submit contribution #{{this.model.id}}</h2>
</header> </header>
<div class="content"> <div class="content">
{{add-contribution attributes=attributes contributors=sortedContributors save=(action "save")}} <AddContribution @attributes={{this.attributes}}
@contributors={{this.sortedContributors}}
@save={{action "save"}} />
</div> </div>
</section> </section>
+1 -1
View File
@@ -6,7 +6,7 @@
</header> </header>
<div class="content"> <div class="content">
{{add-contributor attributes=attributes save=(action "save")}} <AddContributor @attributes={{this.attributes}} @save={{action "save"}} />
</div> </div>
</section> </section>
+1 -1
View File
@@ -6,7 +6,7 @@
</header> </header>
<div class="content"> <div class="content">
{{add-contributor save=(action "save")}} <AddContributor @save={{action "save"}} />
</div> </div>
</section> </section>
+46 -30
View File
@@ -1,42 +1,45 @@
{{#if showIntroText}} {{#if this.showIntroText}}
<div id="intro"> <div id="intro" class={{if this.showDetailsPane "with-details"}}>
<h2> <h2>
Welcome to the contribution dashboard of the Welcome to the contribution dashboard of the
<a href="https://kosmos.org" target="_blank" rel="noopener">Kosmos</a> project! <a href="https://kosmos.org" target="_blank" rel="noreferrer noopener">Kosmos</a> project!
</h2> </h2>
<p> <p>
If you want to learn more about what the numbers mean and how this works, If you want to learn more about what the numbers mean and how this works,
check out the check out the
<a href="https://wiki.kosmos.org/Kredits" target="_blank" rel="noopener">Kredits documentation</a>. <a href="https://wiki.kosmos.org/Kredits" target="_blank" rel="noreferrer noopener">Kredits documentation</a>.
If you want to start earning kredits for your contributions, If you want to start earning kredits for your contributions,
{{link-to "create a contributor profile" "signup"}}. <LinkTo @route="signup">create a contributor profile</LinkTo>.
</p> </p>
</div> </div>
{{/if}} {{/if}}
<main id="dashboard" class={{if showDetailsPane "with-details"}}> <main id="dashboard" class={{if this.showDetailsPane "with-details"}}>
<div id="stats"> <div id="stats">
<section id="people"> <section id="people">
<header class="with-nav"> <header class="with-nav">
<h2>Contributors</h2> <h2>Contributors</h2>
{{#if kredits.hasAccounts}} {{#if this.kredits.hasAccounts}}
<nav> <nav>
{{link-to "add" "contributors.new" title="Add contributor profile" class="button small green"}} <LinkTo @route="contributors.new" @title="Add contributor profile" class="button small green">add</LinkTo>
</nav> </nav>
{{/if}} {{/if}}
</header> </header>
<div class="content"> <div class="content">
{{contributor-list contributorList=kreditsToplist <ContributorList @contributorList={{this.kreditsToplist}}
showUnconfirmedKredits=showUnconfirmedKredits @showUnconfirmedKredits={{this.showUnconfirmedKredits}}
selectedContributorId=selectedContributorId}} @selectedContributorId={{this.selectedContributorId}}
@loading={{this.kredits.syncContributors.isRunning}} />
<p class="stats"> <p class="stats">
<span class="number">{{await kredits.totalKreditsEarned}}</span> kredits confirmed and issued to <span class="number">{{await this.kredits.totalKreditsEarned}}</span> kredits confirmed and issued to
<span class="number">{{contributorsWithKredits.length}}</span> contributors <span class="number">{{this.contributorsWithKredits.length}}</span> contributors
</p> </p>
<p class="stats"> <p class="stats">
{{input type="checkbox" id="hide-unnconfirmed-kredits" checked=showUnconfirmedKredits}} <Input id="hide-unnconfirmed-kredits"
@type="checkbox"
@checked={{this.showUnconfirmedKredits}} />
<label for="hide-unnconfirmed-kredits">Show unconfirmed kredits in toplist</label> <label for="hide-unnconfirmed-kredits">Show unconfirmed kredits in toplist</label>
</p> </p>
</div> </div>
@@ -47,30 +50,39 @@
<h2>Contributions by type</h2> <h2>Contributions by type</h2>
</header> </header>
<div class="content"> <div class="content">
{{chart-contributions-by-type contributions=contributions}} <ChartContributionsByType @contributions={{this.contributions}} />
</div> </div>
</section> </section>
</div> </div>
<div id="contributions"> <div id="contributions">
{{#if contributionsUnconfirmed}} {{#if this.contributionsUnconfirmed}}
<section id="contributions-unconfirmed"> <section id="contributions-unconfirmed">
<header class="with-nav"> <header class="with-nav">
<h2>Latest Contributions</h2> <h2>
Latest Contributions
</h2>
<nav> <nav>
<button {{action "toggleQuickFilterUnconfirmed"}} class="small {{if showQuickFilterUnconfirmed "active"}}">filter</button> <button type="button"
{{#if kredits.hasAccounts}} onclick={{action "toggleQuickFilterUnconfirmed"}}
{{link-to "add" "contributions.new" title="Submit a contribution" class="button small green"}} class="small {{if this.showQuickFilterUnconfirmed "active"}}">
filter
</button>
{{#if this.kredits.hasAccounts}}
<LinkTo @route="contributions.new"
@title="Submit a contribution"
class="button small green">add</LinkTo>
{{/if}} {{/if}}
</nav> </nav>
</header> </header>
<div class="content"> <div class="content">
{{!-- TODO: We need a better naming for kredits.hasAccounts --}} {{!-- TODO: We need a better naming for kredits.hasAccounts --}}
{{contribution-list contributions=contributionsUnconfirmedSorted <ContributionList @contributions={{this.contributionsUnconfirmedSorted}}
vetoContribution=(action "vetoContribution") @vetoContribution={{action "vetoContribution"}}
contractInteractionEnabled=kredits.hasAccounts @contractInteractionEnabled={{this.kredits.hasAccounts}}
selectedContributionId=selectedContributionId @selectedContributionId={{this.selectedContributionId}}
showQuickFilter=showQuickFilterUnconfirmed}} @showQuickFilter={{this.showQuickFilterUnconfirmed}}
@loading={{this.kredits.syncContributions.isRunning}}/>
</div> </div>
</section> </section>
{{/if}} {{/if}}
@@ -79,14 +91,18 @@
<header class="with-nav"> <header class="with-nav">
<h2>Confirmed Contributions</h2> <h2>Confirmed Contributions</h2>
<nav> <nav>
<button {{action "toggleQuickFilterConfirmed"}} class="small {{if showQuickFilterConfirmed "active"}}">filter</button> <button type="button"
onclick={{action "toggleQuickFilterConfirmed"}}
class="small {{if this.showQuickFilterConfirmed "active"}}">
filter
</button>
</nav> </nav>
</header> </header>
<div class="content"> <div class="content">
{{contribution-list contributions=contributionsConfirmedSorted <ContributionList @contributions={{this.contributionsConfirmedSorted}}
vetoContribution=(action "vetoContribution") @vetoContribution={{action "vetoContribution"}}
selectedContributionId=selectedContributionId @selectedContributionId={{this.selectedContributionId}}
showQuickFilter=showQuickFilterConfirmed}} @showQuickFilter={{this.showQuickFilterConfirmed}} />
</div> </div>
</section> </section>
</div> </div>
+20 -20
View File
@@ -1,8 +1,8 @@
<section id="contribution-details" class={{if model.vetoed "vetoed"}}> <section id="contribution-details" class={{if this.model.vetoed "vetoed"}}>
<header class="with-nav"> <header class="with-nav">
<h2>Contribution #{{model.id}}</h2> <h2>Contribution #{{this.model.id}}</h2>
<nav> <nav>
<span class="amount">{{model.amount}}</span>&nbsp; <span class="amount">{{this.model.amount}}</span>&nbsp;
<span class="symbol">₭S</span> <span class="symbol">₭S</span>
</nav> </nav>
</header> </header>
@@ -10,39 +10,39 @@
<div class="content"> <div class="content">
<p class="who-what-when"> <p class="who-what-when">
<span class="contributor"> <span class="contributor">
{{user-avatar contributor=model.contributor}} <UserAvatar @contributor={{this.model.contributor}} />
{{link-to model.contributor.name "dashboard.contributors.show" model.contributor}} <LinkTo @route="dashboard.contributors.show" @model={{this.model.contributor}}>{{this.model.contributor.name}}</LinkTo>
</span> </span>
contributed contributed
<span class="date" title={{model.iso8601Date}}>{{moment-from model.iso8601Date}}</span>: <span class="date" title={{this.model.iso8601Date}}>{{moment-from this.model.iso8601Date}}</span>:
</p> </p>
<h3>{{model.description}}</h3> <h3>{{this.model.description}}</h3>
<p> <p>
Kind: {{model.kind}} Kind: {{this.model.kind}}
<br>Status: {{contribution-status model}} <br>Status: {{contribution-status this.model}}
</p> </p>
{{#if model.url}} {{#if this.model.url}}
<p> <p>
<a href={{model.url}} <a href={{this.model.url}}
title={{model.description}} title={{this.model.description}}
class="button" class="button"
target="_blank" target="_blank"
rel="noopener"> rel="noopener noreferrer">
Open URL Open URL
</a> </a>
</p> </p>
{{/if}} {{/if}}
{{#if model.vetoed}} {{#if this.model.vetoed}}
<div class="hint vetoed"> <div class="hint vetoed">
<div class="icon"> <div class="icon">
{{icon-warning}} <IconWarning />
</div> </div>
<p> <p>
This contribution has been vetoed, meaning no This contribution has been vetoed, meaning no
kredits will be issued. kredits will be issued.
</p> </p>
<p> <p>
{{link-to "Re-submit contribution …" "contributions.resubmit" model class="button small green"}}. <LinkTo @route="contributions.resubmit" @model={{this.model}} class="button small green">Re-submit contribution …</LinkTo>
</p> </p>
</div> </div>
{{/if}} {{/if}}
@@ -50,10 +50,10 @@
<div class="actions"> <div class="actions">
<p> <p>
{{link-to "Copy & edit as new" "contributions.resubmit" model class="button small"}}. <LinkTo @route="contributions.resubmit" @model={{this.model}} class="button small">Copy & edit as new</LinkTo>
{{#if model.ipfsHash}} {{#if this.model.ipfsHash}}
<a href="{{ipfsGatewayUrl}}/{{model.ipfsHash}}" <a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}"
class="button small" target="_blank" rel="noopener"> class="button small" target="_blank" rel="noopener noreferrer">
Inspect IPFS data Inspect IPFS data
</a> </a>
{{/if}} {{/if}}
+17 -12
View File
@@ -1,36 +1,41 @@
<section id="contributor-profile"> <section id="contributor-profile">
<header> <header>
{{user-avatar contributor=model size="medium"}} <UserAvatar @contributor={{this.model}} @size="medium" />
</header> </header>
<div class="content"> <div class="content">
<h2>{{model.name}}</h2> <h2>{{this.model.name}}</h2>
<p class="role">{{roleName}}</p> <p class="role">{{this.roleName}}</p>
<ul class="external-accounts"> <ul class="external-accounts">
{{#each model.accounts as |account|}} {{#each this.model.accounts as |account|}}
<li> <li>
{{external-account-link account=account}} <ExternalAccountLink @account={{account}} />
</li> </li>
{{/each}} {{/each}}
{{#if model.url}} {{#if this.model.url}}
<li> <li>
<a href={{model.url}} target="_blank" rel="noopener" title="Website"> <a href={{this.model.url}} target="_blank" rel="noopener noreferrer" title="Website">
<span class="site">Web</span> <span class="site">Web</span>
{{icon-web-globe}} <IconWebGlobe />
</a> </a>
</li> </li>
{{/if}} {{/if}}
</ul> </ul>
<div class="actions">
<p>
<LinkTo @route="contributions.new" @query={{hash contributorId=this.model.id}} class="button green">♥ Give kredits</LinkTo>
</p>
</div>
</div> </div>
<div class="actions"> <div class="actions">
<p> <p>
<a href="https://rinkeby.etherscan.io/address/{{model.account}}" class="button small" target="_blank" rel="noopener">Inspect Ethereum transactions</a> <a href="https://rinkeby.etherscan.io/address/{{this.model.account}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect Ethereum transactions</a>
{{#if model.ipfsHash}} {{#if this.model.ipfsHash}}
<a href="{{ipfsGatewayUrl}}/{{model.ipfsHash}}" class="button small" target="_blank" rel="noopener">Inspect IPFS profile</a> <a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect IPFS profile</a>
{{/if}} {{/if}}
{{link-to "Edit profile" "contributors.edit" model class="button small"}} <LinkTo @route="contributors.edit" @model={{this.model}} class="button small">Edit profile</LinkTo>
</p> </p>
</div> </div>
</section> </section>
-10
View File
@@ -1,10 +0,0 @@
<section id="add-proposal">
<header>
<h2>Add Proposal</h2>
</header>
<div class="content">
{{add-proposal contributors=minedContributors save=(action "save")}}
</div>
</section>
+1 -1
View File
@@ -10,6 +10,6 @@
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved">in one of our chat rooms</a>?. <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>
<p class="actions"> <p class="actions">
{{#link-to "dashboard" class="button small"}}Return to dashboard{{/link-to}} <LinkTo @route="dashboard" class="button small">Return to dashboard</LinkTo>
</p> </p>
</div> </div>
+5 -6
View File
@@ -6,19 +6,18 @@
Kredits allow you to take part in project governance, and to earn rewards for 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. your contributions. For both, you will need an Ethereum wallet/account.
</p> </p>
<form {{action "submit" on="submit"}}> <form>
<p> <p>
<label> <label>
Ethereum account:<br> Ethereum account:<br>
{{input type="text" value=ethAddress <Input @type="text" @value={{this.ethAddress}} placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" class={{if this.isValidEthAccount "valid" ""}} />
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
class=(if isValidEthAccount "valid" "")}}
</label> </label>
</p> </p>
</form> </form>
<p class="actions"> <p class="actions">
<button disabled={{signupButtonDisabled}} <button disabled={{this.signupButtonDisabled}}
{{action "completeSignup"}}> onclick={{action "completeSignup"}}
type="button">
Complete my profile Complete my profile
</button> </button>
</p> </p>
+2 -2
View File
@@ -12,8 +12,8 @@
connecting one of the following accounts: connecting one of the following accounts:
</p> </p>
<p class="actions"> <p class="actions">
<button {{action "connectGithub"}} class="icon"> <button onclick={{action "connectGithub"}} class="icon" type="button">
{{icon-account-github-com}} <IconAccountGithubCom />
Connect GitHub Connect GitHub
</button> </button>
</p> </p>
-20
View File
@@ -1,20 +0,0 @@
import { computed } from '@ember/object';
import ethers from 'ethers';
export default function(dependentKey, converterMethod) {
return computed(dependentKey, {
get () {
let value = this.get(dependentKey);
if (value && ethers.utils.BigNumber.isBigNumber(value)) {
return value[converterMethod]();
} else {
return value;
}
},
set (key, value) {
const bnValue = ethers.utils.bigNumberify(value);
this.set(dependentKey, bnValue);
return bnValue[converterMethod]();
}
});
}
-17
View File
@@ -1,17 +0,0 @@
import { computed } from '@ember/object';
import ethers from 'ethers';
import formatKredits from 'kredits-web/utils/format-kredits';
export default function(dependentKey, options = {}) {
return computed(dependentKey, {
get () {
const value = this.get(dependentKey);
return formatKredits(value, options);
},
set (key, value) {
const bnValue = ethers.utils.bigNumberify(value);
this.set(dependentKey, bnValue);
return formatKredits(bnValue, options);
}
});
}
+21
View File
@@ -0,0 +1,21 @@
export default function processContributionData(data) {
const processed = {}
if (data.confirmedAtBlock && (typeof data.confirmedAtBlock.toNumber === 'function')) {
processed.confirmedAt = data.confirmedAtBlock.toNumber();
} else if (data.confirmedAt !== 'undefined') {
processed.confirmedAt = data.confirmedAt;
}
const otherProperties = [
'id', 'contributorId', 'amount', 'vetoed',
'ipfsHash', 'kind', 'description', 'details',
'url', 'date', 'time', 'pendingTx'
];
otherProperties.forEach(prop => {
processed[prop] = data[prop];
});
return processed;
}
+19
View File
@@ -0,0 +1,19 @@
export default function processContributorData(data) {
const processed = {
id: data.id.toString(),
balance: data.balanceInt,
totalKreditsEarned: data.totalKreditsEarned,
contributionsCount: data.contributionsCount?.toNumber()
}
const otherProperties = [
'account', 'accounts', 'ipfsHash', 'isCore', 'kind', 'name', 'url',
'github_username', 'github_uid', 'wiki_username', 'zoom_display_name'
];
otherProperties.forEach(prop => {
processed[prop] = data[prop];
});
return processed;
}
+20
View File
@@ -0,0 +1,20 @@
{
"schemaVersion": "1.0.0",
"packages": [
{
"name": "ember-cli",
"version": "3.18.0",
"blueprints": [
{
"name": "app",
"outputRepo": "https://github.com/ember-cli/ember-new-output",
"codemodsSource": "ember-app-codemods-manifest@1",
"isBaseBlueprint": true,
"options": [
"--no-welcome"
]
}
]
}
]
}
+4 -1
View File
@@ -1,3 +1,6 @@
{ {
"jquery-integration": true "application-template-wrapper": false,
"default-async-observers": true,
"jquery-integration": false,
"template-only-glimmer-components": true
} }
+10707 -3113
View File
File diff suppressed because it is too large Load Diff
+43 -33
View File
@@ -1,6 +1,6 @@
{ {
"name": "kredits-web", "name": "kredits-web",
"version": "1.13.0", "version": "1.17.0",
"private": true, "private": true,
"description": "Contribution dashboard of the Kosmos project", "description": "Contribution dashboard of the Kosmos project",
"repository": "https://github.com/67P/kredits-web", "repository": "https://github.com/67P/kredits-web",
@@ -11,12 +11,14 @@
"test": "tests" "test": "tests"
}, },
"scripts": { "scripts": {
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*",
"lint:hbs": "ember-template-lint .", "lint:hbs": "ember-template-lint .",
"lint:js": "eslint .", "lint:js": "eslint .",
"test": "ember test",
"start": "ember serve", "start": "ember serve",
"test": "npm-run-all lint:* test:*",
"test:ember": "ember test",
"start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember serve", "start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember serve",
"build": "ember build", "build": "ember build --environment=production",
"build-prod": "rm -rf release/* && ember build -prod --output-path release", "build-prod": "rm -rf release/* && ember build -prod --output-path release",
"preversion": "npm test", "preversion": "npm test",
"version": "npm run build-prod && git add release/", "version": "npm run build-prod && git add release/",
@@ -24,53 +26,61 @@
"deploy": "git push 5apps master" "deploy": "git push 5apps master"
}, },
"devDependencies": { "devDependencies": {
"@ember/jquery": "^0.6.0", "@ember/optional-features": "^1.3.0",
"@ember/optional-features": "^0.7.0", "@glimmer/component": "^1.0.0",
"@glimmer/tracking": "^1.0.0",
"@kosmos/schemas": "^2.2.1",
"babel-eslint": "^10.1.0",
"babel-preset-es2015": "^6.22.0", "babel-preset-es2015": "^6.22.0",
"babelify": "^7.3.0", "babelify": "^7.3.0",
"brfs": "^2.0.2", "brfs": "^2.0.2",
"broccoli-asset-rev": "^3.0.0", "broccoli-asset-rev": "^3.0.0",
"ember-ajax": "^5.0.0",
"ember-auto-import": "^1.5.3", "ember-auto-import": "^1.5.3",
"ember-awesome-macros": "0.41.0", "ember-awesome-macros": "0.41.0",
"ember-cli": "~3.10.1", "ember-cli": "~3.18.0",
"ember-cli-app-version": "^3.2.0", "ember-cli-app-version": "^3.2.0",
"ember-cli-babel": "^7.7.3", "ember-cli-babel": "^7.20.4",
"ember-cli-chart": "^3.4.0", "ember-cli-chart": "^3.6.0",
"ember-cli-dependency-checker": "^3.1.0", "ember-cli-dependency-checker": "^3.2.0",
"ember-cli-eslint": "^5.1.0", "ember-cli-htmlbars": "^5.1.2",
"ember-cli-htmlbars": "^3.0.1", "ember-cli-inject-live-reload": "^2.0.2",
"ember-cli-htmlbars-inline-precompile": "^2.1.0", "ember-cli-moment-shim": "^3.8.0",
"ember-cli-inject-live-reload": "^1.8.2", "ember-cli-sass": "^10.0.1",
"ember-cli-moment-shim": "^3.7.1",
"ember-cli-sass": "^7.2.0",
"ember-cli-sri": "^2.1.1", "ember-cli-sri": "^2.1.1",
"ember-cli-template-lint": "^1.0.0-beta.1", "ember-cli-uglify": "^3.0.0",
"ember-cli-uglify": "^2.1.0", "ember-concurrency": "^1.1.7",
"ember-export-application-global": "^2.0.0", "ember-export-application-global": "^2.0.1",
"ember-flatpickr": "^2.14.0", "ember-fetch": "^8.0.1",
"ember-load-initializers": "^2.0.0", "ember-flatpickr": "^2.16.2",
"ember-load-initializers": "^2.1.1",
"ember-macro-helpers": "0.17.0", "ember-macro-helpers": "0.17.0",
"ember-maybe-import-regenerator": "^0.1.6", "ember-maybe-import-regenerator": "^0.1.6",
"ember-moment": "^7.8.1", "ember-moment": "^8.0.0",
"ember-promise-helpers": "1.0.6", "ember-promise-helpers": "^1.0.9",
"ember-qunit": "^4.4.1", "ember-qunit": "^4.6.0",
"ember-resolver": "^5.0.1", "ember-resolver": "^8.0.0",
"ember-source": "~3.10.0", "ember-source": "~3.18.0",
"ember-template-lint": "^2.8.0",
"ember-truth-helpers": "github:jmurphyau/ember-truth-helpers#31a14373a31f1f82c77537720549b47a95c28e5f", "ember-truth-helpers": "github:jmurphyau/ember-truth-helpers#31a14373a31f1f82c77537720549b47a95c28e5f",
"eslint-plugin-ember": "^6.2.0", "eslint": "^7.1.0",
"eslint-plugin-node": "^9.0.1", "eslint-plugin-ember": "^8.5.2",
"ethers": "^4.0.27", "eslint-plugin-node": "^11.1.0",
"kosmos-schemas": "^2.0.0", "ethers": "^4.0.47",
"kredits-contracts": "^5.5.0", "kredits-contracts": "^5.5.0",
"loader.js": "^4.7.0", "loader.js": "^4.7.0",
"localforage": "^1.7.3",
"ndjson": "github:hugomrdias/ndjson#feat/readable-stream3", "ndjson": "github:hugomrdias/ndjson#feat/readable-stream3",
"qunit-dom": "^0.8.4", "npm-run-all": "^4.1.5",
"qunit-dom": "^1.2.0",
"sass": "^1.26.7",
"transform-loader": "^0.2.4", "transform-loader": "^0.2.4",
"tv4": "^1.3.0", "tv4": "^1.3.0",
"web3-utils": "^1.0.0" "web3-utils": "^1.2.8"
}, },
"engines": { "engines": {
"node": "8.* || >= 10.*" "node": "10.* || >= 12"
},
"ember": {
"edition": "octane"
} }
} }
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
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
+5 -5
View File
@@ -8,10 +8,10 @@
<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">
<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.13.0%2B15e5d3c2%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" /> <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_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_DEFAULT_ASYNC_OBSERVERS%22%3Atrue%2C%22_JQUERY_INTEGRATION%22%3Afalse%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.17.0%2B733e5e36%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/vendor-ca96b2e19fc9f356e3dbad90c9f3f323.css">
<link integrity="" rel="stylesheet" href="/assets/kredits-web-7486572f1c1005f28df459da134a97f2.css"> <link integrity="" rel="stylesheet" href="/assets/kredits-web-f225d49328a14feac0888a2126087423.css">
@@ -24,8 +24,8 @@
<body> <body>
<script src="/assets/vendor-21c31ba5343c7b72f638899d335a0531.js" integrity="sha256-5QKR4/TvbS9UoThtAlcyvj54wU+Bcn0hnM0FzjUbvfA= sha512-tHxWM5t9FYFz6DBnXuzWiIMPPW/gNwPaFDOdUL32cWvWcMfjyrhIEPM5I1D6+HQE1kXCEd64a+7UiMdU2hH5JQ==" ></script> <script src="/assets/vendor-c96bc8b15817400d8cb58a32671bab0c.js" integrity="sha256-vRS2UvkDzerqXqzdCMPtGtxCDFflTcDv9xfi/8NLKJw= sha512-GWfVwFu8LhNRJyijvRuow7f38ARQGGCU0uKzB7HwiX+pQF6Vw5zSfZHtlfZiucFImmb2uyxSexiXkh16ulShEw==" ></script>
<script src="/assets/kredits-web-a6b23d76eb470475515cfa602440fbfb.js" integrity="sha256-0kjKqEi3xMDzRlsV7Z+2x9xXF5AAi0EFVSLPD16NdH8= sha512-9IuLwhzOfgxZKJZ2L2sy4zoibavROIwKU4TRoNYGdzkVqPUhNOhT5i3BLgbRrZ57oFm5WwfjVyB6RAoVjVz06w==" ></script> <script src="/assets/kredits-web-b7d7786c26ec2d9d78fc58c35d4e9c59.js" integrity="sha256-IUbTJl1JYq98BlScoUafRjtznFRb4LOaGC0weMP0ycU= sha512-kzcGqMHh8+3DLnSeTwK3Mw6Nr+JZw0EEjfMGx+c/D1OkAVvuKXYmUKq1b5XgFw+lQ7cDkGLiAb4ud6O4DpT3Jg==" ></script>
</body> </body>
+3 -1
View File
@@ -1,3 +1,5 @@
'use strict';
module.exports = { module.exports = {
test_page: 'tests/index.html?hidepassed', test_page: 'tests/index.html?hidepassed',
disable_watching: true, disable_watching: true,
@@ -7,13 +9,13 @@ module.exports = {
launch_in_dev: [ launch_in_dev: [
'Chrome' 'Chrome'
], ],
browser_start_timeout: 120,
browser_args: { browser_args: {
Chrome: { Chrome: {
ci: [ ci: [
// --no-sandbox is needed when running Chrome inside a container // --no-sandbox is needed when running Chrome inside a container
process.env.CI ? '--no-sandbox' : null, process.env.CI ? '--no-sandbox' : null,
'--headless', '--headless',
'--disable-gpu',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-software-rasterizer', '--disable-software-rasterizer',
'--mute-audio', '--mute-audio',
File diff suppressed because one or more lines are too long
+13 -11
View File
@@ -1,23 +1,25 @@
import Model from 'kredits-web/models/contribution'; import Model from 'kredits-web/models/contribution';
import contributors from '../../tests/fixtures/contributors'; import contributors from '../../tests/fixtures/contributors';
import processContributionData from 'kredits-web/utils/process-contribution-data';
const items = []; const items = [];
const data = [ const data = [
{ id: 1, contributorId: 1, confirmedAtBlock: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'dev' }, { id: 1, contributorId: 1, confirmedAt: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'dev' },
{ id: 2, contributorId: 1, confirmedAtBlock: 1000, claimed: false, vetoed: false, amount: 5000, kind: 'ops' }, { id: 2, contributorId: 1, confirmedAt: 1000, claimed: false, vetoed: false, amount: 5000, kind: 'ops' },
{ id: 3, contributorId: 2, confirmedAtBlock: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'ops' }, { id: 3, contributorId: 2, confirmedAt: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'ops' },
{ id: 4, contributorId: 2, confirmedAtBlock: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'docs' }, { id: 4, contributorId: 2, confirmedAt: 1000, claimed: false, vetoed: false, amount: 1500, kind: 'docs' },
{ id: 5, contributorId: 1, confirmedAtBlock: 1000, claimed: false, vetoed: false, amount: 5000, kind: 'design' }, { id: 5, contributorId: 1, confirmedAt: 1000, claimed: false, vetoed: false, amount: 5000, kind: 'design' },
{ id: 6, contributorId: 1, confirmedAtBlock: 1000, claimed: false, vetoed: true, amount: 500, kind: 'dev' }, { id: 6, contributorId: 1, confirmedAt: 1000, claimed: false, vetoed: true, amount: 500, kind: 'dev' },
{ id: 7, contributorId: 3, confirmedAtBlock: 2000, claimed: false, vetoed: false, amount: 5000, kind: 'dev' }, { id: 7, contributorId: 3, confirmedAt: 2000, claimed: false, vetoed: false, amount: 5000, kind: 'dev' },
{ id: 8, contributorId: 1, confirmedAtBlock: 2000, claimed: false, vetoed: false, amount: 1500, kind: 'community' }, { id: 8, contributorId: 1, confirmedAt: 2000, claimed: false, vetoed: false, amount: 1500, kind: 'community' },
{ id: 9, contributorId: 3, confirmedAtBlock: 2000, claimed: false, vetoed: true, amount: 1500, kind: 'docs' }, { id: 9, contributorId: 3, confirmedAt: 2000, claimed: false, vetoed: true, amount: 1500, kind: 'docs' },
]; ];
data.forEach(attrs => { data.forEach(attrs => {
attrs.contributor = contributors.findBy('id', attrs.contributorId.toString()); const c = Model.create(processContributionData(attrs));
items.push(Model.create(attrs)) c.set('contributor', contributors.findBy('id', attrs.contributorId.toString()));
items.push(c);
}); });
export default items; export default items;
+64
View File
@@ -0,0 +1,64 @@
export default {
"0": 1,
"1": "0x7E8f313C56F809188313aa274Fa67EE58c31515d",
"2": "0x99b8afd7b266e19990924a8be9099e81054b70c36b20937228a77a5cf75723b8",
"3": 18,
"4": 32,
"5": true,
"6": {
"_hex": "0x09979c0838e8fc880000"
},
"7": 53500,
"8": {
"_hex": "0x49"
},
"9": true,
"id": 1,
"account": "0x7E8f313C56F809188313aa274Fa67EE58c31515d",
"hashDigest": "0x99b8afd7b266e19990924a8be9099e81054b70c36b20937228a77a5cf75723b8",
"hashFunction": 18,
"hashSize": 32,
"isCore": true,
"balance": {
"_hex": "0x09979c0838e8fc880000"
},
"totalKreditsEarned": 53500,
"contributionsCount": {
"_hex": "0x49",
"toNumber": function() { return 73; }
},
"exists": true,
"balanceInt": 45298,
"ipfsHash": "QmYgiRd1FZ7JjDGBwkmLQNF6XQuyF8AWoFDRvUWhhmxiEj",
"name": "Bumi",
"kind": "person",
"url": "https://michaelbumann.com",
"accounts": [
{
"site": "github.com",
"uid": 318,
"username": "bumi",
"url": "https://github.com/bumi"
},
{
"site": "gitea.kosmos.org",
"username": "bumi",
"url": "https://gitea.kosmos.org/bumi"
},
{
"site": "wiki.kosmos.org",
"username": "Bumi",
"url": "https://wiki.kosmos.org/User:Bumi"
},
{
"site": "zoom.us",
"username": "bumi"
}
],
"github_uid": 318,
"github_username": "bumi",
"gitea_username": "bumi",
"wiki_username": "Bumi",
"zoom_display_name": "bumi",
"ipfsData": "{\n \"@context\": \"https://schema.kosmos.org\",\n \"@type\": \"Contributor\",\n \"kind\": \"person\",\n \"name\": \"Bumi\",\n \"accounts\": [\n {\n \"site\": \"github.com\",\n \"uid\": 318,\n \"username\": \"bumi\",\n \"url\": \"https://github.com/bumi\"\n },\n {\n \"site\": \"gitea.kosmos.org\",\n \"username\": \"bumi\",\n \"url\": \"https://gitea.kosmos.org/bumi\"\n },\n {\n \"site\": \"wiki.kosmos.org\",\n \"username\": \"Bumi\",\n \"url\": \"https://wiki.kosmos.org/User:Bumi\"\n },\n {\n \"site\": \"zoom.us\",\n \"username\": \"bumi\"\n }\n ],\n \"url\": \"https://michaelbumann.com\"\n}"
}
+8 -4
View File
@@ -1,13 +1,17 @@
import Contributor from 'kredits-web/models/contributor'; import Contributor from 'kredits-web/models/contributor';
import processContributorData from 'kredits-web/utils/process-contributor-data';
const contributors = []; const contributors = [];
const data = [ const data = [
{ id: 1, name: 'Bumi', totalKreditsEarned: 11500, github_uid: 318 }, { id: '1', name: 'Bumi', totalKreditsEarned: 11500, github_uid: 318 },
{ id: 2, name: 'Râu Cao', totalKreditsEarned: 3000, github_uid: 842 }, { id: '2', name: 'Râu Cao', totalKreditsEarned: 3000, github_uid: 842 },
{ id: 3, name: 'Manuel', totalKreditsEarned: 0, github_uid: 54812 } { id: '3', name: 'Manuel', totalKreditsEarned: 0, github_uid: 54812 }
]; ];
data.forEach(attrs => contributors.push(Contributor.create(attrs))); data.forEach(attrs => {
const c = Contributor.create(processContributorData(attrs));
contributors.push(c);
});
export default contributors; export default contributors;
@@ -1,18 +0,0 @@
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 | add proposal', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
await render(hbs`{{add-proposal}}`);
assert.dom('.actions a').hasText('Back');
});
});
@@ -6,7 +6,7 @@ import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | chart-contributions-by-type', function(hooks) { module('Integration | Component | chart-contributions-by-type', function(hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
let proposals = [ let contributions = [
{ kind: 'dev', amount: 500 }, { kind: 'dev', amount: 500 },
{ kind: 'dev', amount: 1500 }, { kind: 'dev', amount: 1500 },
{ kind: 'ops', amount: 1500 }, { kind: 'ops', amount: 1500 },
@@ -20,9 +20,9 @@ module('Integration | Component | chart-contributions-by-type', function(hooks)
]; ];
test('it renders', async function(assert) { test('it renders', async function(assert) {
this.set('proposals', proposals); this.set('contributions', contributions);
await render(hbs`{{chart-contributions-by-type contributions=proposals}}`); await render(hbs`{{chart-contributions-by-type contributions=contributions}}`);
assert.dom(this.element).hasText(''); assert.dom(this.element).hasText('');
}); });
@@ -1,18 +0,0 @@
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 | proposal list', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });
await render(hbs`{{proposal-list}}`);
assert.dom('*').hasText('');
});
});
@@ -29,7 +29,7 @@ module('Integration | Component | topbar-account-panel', function(hooks) {
}); });
await render(hbs`<TopbarAccountPanel />`); await render(hbs`<TopbarAccountPanel />`);
assert.equal(this.element.textContent.trim(), 'Dorian Nakamoto'); assert.dom(this.element).hasText('Dorian Nakamoto');
service.set('currentUser.isCore', true); service.set('currentUser.isCore', true);
await render(hbs`<TopbarAccountPanel />`); await render(hbs`<TopbarAccountPanel />`);
@@ -0,0 +1,41 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import moment from 'moment';
module('Unit | Component | add-contribution', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let component = this.owner.factoryFor('component:add-contribution').create();
assert.ok(component);
});
test('default attributes', function(assert) {
let component = this.owner.factoryFor('component:add-contribution').create();
['contributorId', 'kind', 'amount', 'description', 'url', 'details'].forEach(a => {
assert.equal(component.attributes[a], null, `sets the default ${a} attribute`);
assert.equal(component.get(a), null, `sets the ${a} property`);
});
assert.ok(moment.isDate(component.attributes.date), 'sets the default date attribute');
assert.ok(moment.isDate(component.date), 'sets the default date');
});
test('override default attributes', function(assert) {
let component = this.owner.factoryFor('component:add-contribution').create({
attributes: { contributorId: '1' }
});
assert.equal(component.contributorId, '1', `overrides the default property`);
['kind', 'amount', 'description', 'url', 'details'].forEach(a => {
assert.equal(component.attributes[a], null, `sets the default ${a} attribute`);
assert.equal(component.get(a), null, `sets the ${a} property`);
});
assert.ok(moment.isDate(component.attributes.date), 'sets the default date attribute');
assert.ok(moment.isDate(component.date), 'sets the default date');
});
});
@@ -1,12 +0,0 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Controller | proposals/new', function(hooks) {
setupTest(hooks);
// Replace this with your real tests.
test('it exists', function(assert) {
let controller = this.owner.lookup('controller:proposals/new');
assert.ok(controller);
});
});

Some files were not shown because too many files have changed in this diff Show More