Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c57d4162d7 | |||
| 41cc37d3eb | |||
| 081584a508 | |||
| 3cc01cce4e | |||
| cedbe3cb3d | |||
| b8da04c145 | |||
| 009caa53e5 | |||
| 9a1f543921 | |||
| 22300c2980 | |||
| 018132034d | |||
| f5d7916556 | |||
| 12df28f7fd | |||
| 2f4f350a93 | |||
| 4946b3311d | |||
| cc2f91afce | |||
| aa351cbe17 | |||
| 065c635580 | |||
| 67e3c9a7c3 | |||
| 4f4b3c5c17 | |||
| 4f6bafa31e | |||
| 94846c6e00 | |||
| 45d553866e | |||
| 8ac6e3ad3c | |||
| 215ad1d640 | |||
| 3f4cc39067 | |||
| 0a6b6180b9 | |||
| 536868002f | |||
| 04a7f9139d | |||
| 863b542e3e | |||
| 23d86f1502 | |||
| 314cf58c29 | |||
| 9c063b82f1 | |||
| b397e4faf6 | |||
| 256197094e | |||
| 9898d4bb1d | |||
| c321dc51b0 | |||
| 8c759601ca | |||
| 15e5d3c2fb | |||
| 47b7a6501b | |||
| 42304a8e27 | |||
| 9a4db6daf6 | |||
| 26dc78f553 | |||
| 5a618f7ef9 | |||
| 5d29bd9604 | |||
| 97cc71024f | |||
| 58f1382df1 | |||
| 6c5406c0b9 | |||
| b2457421be | |||
| 134eb7d355 | |||
| 1dc1a9767e | |||
| 243a41db88 | |||
| 523d4524b5 | |||
| c21613f20a | |||
| 12b127c75d | |||
| a7dd058d21 | |||
| de6606b29d | |||
| ec701b3130 | |||
| 8e7430ebb9 | |||
| 1deecafee7 | |||
| 51d8b6c8c1 | |||
| b28f6e6bd7 | |||
| 4ab28016a3 | |||
| 6995b67a22 | |||
| 0076d59a42 | |||
| 36b92d97ca | |||
| f272bcfcbc | |||
| 985e1673ac | |||
| fe4af46852 | |||
| 7b9659e22f | |||
| 8be18fc27c | |||
| 7e518780c5 | |||
| 89391c1543 | |||
| 05e3118a0c | |||
| 494b5d9bdd | |||
| f8f1ad644c | |||
| 5a5051bac6 | |||
| 304c0ac8d0 | |||
| cae13ed662 | |||
| 729110f8d1 | |||
| 11f66de6e6 | |||
| 9bf2ae2d7f | |||
| 2a384f3eed | |||
| 6b3666def9 | |||
| 74c8e32cc6 | |||
| 5dde978499 | |||
| 0625ee9d79 | |||
| dc5dfe19f2 | |||
| c518b7f565 | |||
| d13242b210 | |||
| ada029586a | |||
| 7bf5fa444a | |||
| d08d5f9e8d | |||
| 60ba019139 | |||
| 413bcddb1e | |||
| c2273d1778 | |||
| 780afc9bc6 | |||
| a8ae2b0156 | |||
| d6d2b1a61c | |||
| 4c70c0d233 | |||
| 725d36bed6 | |||
| 0a71d7ad91 | |||
| 6a176b5904 | |||
| c7d046aa46 |
@@ -4,7 +4,6 @@
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
sourceType: 'module'
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
legacyDecorators: true
|
||||
}
|
||||
},
|
||||
globals: {
|
||||
console: true
|
||||
@@ -19,7 +25,10 @@ module.exports = {
|
||||
},
|
||||
rules: {
|
||||
'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: [
|
||||
// node files
|
||||
@@ -35,8 +44,7 @@ module.exports = {
|
||||
'server/**/*.js'
|
||||
],
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
ecmaVersion: 2015
|
||||
sourceType: 'script'
|
||||
},
|
||||
env: {
|
||||
browser: false,
|
||||
|
||||
@@ -1,10 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
extends: 'recommended',
|
||||
extends: 'octane',
|
||||
|
||||
rules: {
|
||||
'simple-unless': 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -23,7 +23,9 @@ env:
|
||||
# See https://git.io/vdao3 for details.
|
||||
- JOBS=1
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
script:
|
||||
- npm run lint:hbs
|
||||
- npm run lint:js
|
||||
- npm test
|
||||
|
||||
@@ -71,10 +71,10 @@ These are the basic steps to get up and running:
|
||||
|
||||
#### 1. IPFS
|
||||
|
||||
Run a local IPFS deamon in offline mode.
|
||||
Run a local IPFS deamon.
|
||||
|
||||
* Make sure CORS headers are configured. See [IPFS](#ipfs) for more info.
|
||||
* `ipfs daemon --offline`
|
||||
* `ipfs daemon`
|
||||
|
||||
#### 2. kredits-contracts
|
||||
|
||||
@@ -94,11 +94,15 @@ With IPFS and Ethereum/ganache running, you can now start this Ember app.
|
||||
* `npm link kredits-contracts` - link the local `kredits-contracts` package
|
||||
* `npm run start:local` - WEB3_PROVIDER_URL=http://localhost:7545 must be set for local settings
|
||||
|
||||
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO Kernel address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_KERNEL_ADDRESS` environment variable.
|
||||
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO address.
|
||||
If that is the case the DAO address must be provided as `KREDITS_DAO_ADDRESS` environment variable.
|
||||
|
||||
* `npm run dao:address` in the kredits-contracts repo to find your address
|
||||
* `KREDITS_KERNEL_ADDRESS=<0xYOURADDRESS> npm run start:local`
|
||||
* `KREDITS_DAO_ADDRESS=<0xYOURADDRESS> npm run start:local`
|
||||
|
||||
#### 4. Metamask network
|
||||
|
||||
Switch the network in Metamask to "Custom RPC" with the RPC URL `http://localhost:7545`.
|
||||
|
||||
#### IPFS
|
||||
|
||||
@@ -108,6 +112,7 @@ Install IPFS with your favorite package manager and run
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["localhost:4200"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
|
||||
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
|
||||
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8080
|
||||
|
||||
## Further Reading / Useful Links
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import Application from '@ember/application';
|
||||
import Resolver from './resolver';
|
||||
import Resolver from 'ember-resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
|
||||
const App = Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
});
|
||||
export default class App extends Application {
|
||||
modulePrefix = config.modulePrefix;
|
||||
podModulePrefix = config.podModulePrefix;
|
||||
Resolver = Resolver;
|
||||
}
|
||||
|
||||
loadInitializers(App, config.modulePrefix);
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { assign } from '@ember/polyfills';
|
||||
import moment from 'moment';
|
||||
|
||||
export default Component.extend({
|
||||
@@ -23,17 +24,18 @@ export default Component.extend({
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('defaultDate', moment().startOf('hour').toDate());
|
||||
|
||||
// Default attributes used by reset
|
||||
this.set('attributes', {
|
||||
this.set('defaultAttr', {
|
||||
contributorId: null,
|
||||
kind: null,
|
||||
date: this.defaultDate,
|
||||
amount: null,
|
||||
description: null,
|
||||
url: null,
|
||||
details: null
|
||||
});
|
||||
|
||||
this.set('attributes', assign({}, this.defaultAttr, this.attributes));
|
||||
|
||||
this.reset();
|
||||
},
|
||||
|
||||
@@ -43,7 +45,9 @@ export default Component.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
submit () {
|
||||
submit (evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
if (!this.isValid) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
@@ -67,7 +71,6 @@ export default Component.extend({
|
||||
window.alert('Something went wrong. Check the browser console for details.');
|
||||
})
|
||||
.finally(() => this.set('inProgress', false));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<form onsubmit={{action "submit"}}>
|
||||
<label>
|
||||
<p class="label">Contributor:</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>
|
||||
{{#each contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{#each @contributors as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</p>
|
||||
@@ -13,14 +13,14 @@
|
||||
<label>
|
||||
<p class="label">Kind:</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="community" selected={{eq kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
|
||||
<option value="special" selected={{eq kind "special"}}>Special</option>
|
||||
<option value="community" selected={{eq this.kind "community"}}>Community</option>
|
||||
<option value="design" selected={{eq this.kind "design"}}>Design</option>
|
||||
<option value="dev" selected={{eq this.kind "dev"}}>Development</option>
|
||||
<option value="docs" selected={{eq this.kind "docs"}}>Documentation</option>
|
||||
<option value="ops" selected={{eq this.kind "ops"}}>IT Operations</option>
|
||||
<option value="special" selected={{eq this.kind "special"}}>Special</option>
|
||||
</select>
|
||||
</p>
|
||||
</label>
|
||||
@@ -28,12 +28,12 @@
|
||||
<p class="label">Date:</p>
|
||||
<p>
|
||||
{{ember-flatpickr
|
||||
date=date
|
||||
defaultDate=defaultDate
|
||||
maxDate=defaultDate
|
||||
date=this.date
|
||||
defaultDate=this.defaultDate
|
||||
maxDate=this.defaultDate
|
||||
enableTime=true
|
||||
time_24hr=true
|
||||
onChange=(action (mut date))
|
||||
onChange=(action (mut this.date))
|
||||
}}
|
||||
</p>
|
||||
</label>
|
||||
@@ -42,31 +42,42 @@
|
||||
<p>
|
||||
{{input type="text"
|
||||
placeholder="500"
|
||||
value=amount
|
||||
class=(if isValidAmount "valid" "")}}
|
||||
value=this.amount
|
||||
class=(if this.isValidAmount "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">Description:</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=description
|
||||
class=(if isValidDescription "valid" "")}}
|
||||
value=this.description
|
||||
class=(if this.isValidDescription "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
<label>
|
||||
<p class="label">URL (optional):</p>
|
||||
<p>
|
||||
{{input type="text"
|
||||
value=url
|
||||
class=(if isValidUrl "valid" "")}}
|
||||
value=this.url
|
||||
class=(if this.isValidUrl "valid" "")}}
|
||||
</p>
|
||||
</label>
|
||||
|
||||
{{#if this.details}}
|
||||
<label>
|
||||
<p class="label">Details:</p>
|
||||
<p>
|
||||
<pre>
|
||||
{{this.details}}
|
||||
</pre>
|
||||
</p>
|
||||
</label>
|
||||
{{/if}}
|
||||
|
||||
<p class="actions">
|
||||
{{input type="submit"
|
||||
disabled=inProgress
|
||||
value=(if inProgress "Processing" "Save")}}
|
||||
disabled=this.inProgress
|
||||
value=(if this.inProgress "Processing" "Save")}}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { and, notEmpty } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import { isAddress } from 'web3-utils';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
router: service(),
|
||||
kredits: service(),
|
||||
|
||||
attributes: null,
|
||||
|
||||
// TODO: add proper address validation
|
||||
isValidAccount: notEmpty('account'),
|
||||
isValidAccount: computed('account', function() {
|
||||
return isAddress(this.account);
|
||||
}),
|
||||
|
||||
isValidName: notEmpty('name'),
|
||||
isValidURL: notEmpty('url'),
|
||||
isValidGithubUID: notEmpty('github_uid'),
|
||||
isValidGithubUsername: notEmpty('github_username'),
|
||||
isValidGiteaUsername: notEmpty('gitea_username'),
|
||||
isValidWikiUsername: notEmpty('wiki_username'),
|
||||
isValidZoomDisplayName: notEmpty('zoom_display_name'),
|
||||
|
||||
isValid: and(
|
||||
'isValidAccount',
|
||||
'isValidName',
|
||||
@@ -42,7 +49,8 @@ export default Component.extend({
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
gitea_username: null,
|
||||
wiki_username: null
|
||||
wiki_username: null,
|
||||
zoom_display_name: null
|
||||
});
|
||||
},
|
||||
|
||||
@@ -52,9 +60,15 @@ export default Component.extend({
|
||||
|
||||
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) {
|
||||
alert('Invalid data. Please review and try again.');
|
||||
window.alert('Invalid data. Please review and try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -70,6 +84,7 @@ export default Component.extend({
|
||||
window.alert('Something went wrong. Please check the browser console.');
|
||||
}).finally(() => {
|
||||
this.set('inProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,56 @@
|
||||
<form {{action "submit" on="submit"}}>
|
||||
<form onsubmit={{action "submit"}}>
|
||||
<p>
|
||||
<label for="c-account">Ethereum account</label>
|
||||
{{input name="account" id="c-account"
|
||||
type="text" value=account
|
||||
<Input @type="text"
|
||||
@value={{this.account}}
|
||||
name="account" id="c-account"
|
||||
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
|
||||
class=(if isValidAccount "valid" "")}}
|
||||
class={{if this.isValidAccount "valid" ""}} />
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-kind">Kind</label>
|
||||
<select required onchange={{action (mut kind) value="target.value"}} id="c-kind">
|
||||
<option value="person" selected={{eq kind "person"}}>Person</option>
|
||||
<option value="organization" selected={{eq kind "organization"}}>Organization</option>
|
||||
<select required onchange={{action (mut this.kind) value="target.value"}} id="c-kind">
|
||||
<option value="person" selected={{eq this.kind "person"}}>Person</option>
|
||||
<option value="organization" selected={{eq this.kind "organization"}}>Organization</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-name">Name</label>
|
||||
{{input name="name" type="text" value=name placeholder="Zero Cool"
|
||||
class=(if isValidName "valid" "") id="c-name"}}
|
||||
{{input name="name" type="text" value=this.name placeholder="Zero Cool"
|
||||
class=(if this.isValidName "valid" "") id="c-name"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-url">URL</label>
|
||||
{{input name="url" type="text" value=url placeholder="http://zerocool.bit"
|
||||
class=(if isValidURL "valid" "") id="c-url"}}
|
||||
{{input name="url" type="text" value=this.url placeholder="http://zerocool.bit"
|
||||
class=(if this.isValidURL "valid" "") id="c-url"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-uid">GitHub UID</label>
|
||||
{{input name="github_uid" type="text" value=github_uid placeholder="2342"
|
||||
class=(if isValidGithubUID "valid" "") id="c-github-uid"}}
|
||||
{{input name="github_uid" type="text" value=this.github_uid placeholder="2342"
|
||||
class=(if this.isValidGithubUID "valid" "") id="c-github-uid"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-github-username">GitHub username</label>
|
||||
{{input name="github_username" type="text" value=github_username placeholder="zerocool"
|
||||
class=(if isValidGithubUsername "valid" "") id="c-github-username"}}
|
||||
{{input name="github_username" type="text" value=this.github_username placeholder="zerocool"
|
||||
class=(if this.isValidGithubUsername "valid" "") id="c-github-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-gitea-username">Gitea username</label>
|
||||
{{input name="gitea_username" type="text" value=gitea_username placeholder="zerocool"
|
||||
class=(if isValidGiteaUsername "valid" "") id="c-gitea-username"}}
|
||||
{{input name="gitea_username" type="text" value=this.gitea_username placeholder="zerocool"
|
||||
class=(if this.isValidGiteaUsername "valid" "") id="c-gitea-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-wiki-username">Wiki username</label>
|
||||
{{input name="wiki_username" type="text" value=wiki_username placeholder="ZeroCool"
|
||||
class=(if isValidWikiUsername "valid" "") id="c-wiki-username"}}
|
||||
{{input name="wiki_username" type="text" value=this.wiki_username placeholder="ZeroCool"
|
||||
class=(if this.isValidWikiUsername "valid" "") id="c-wiki-username"}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="c-zoom-display-name">Zoom display name</label>
|
||||
{{input name="zoom_display_name" type="text" value=this.zoom_display_name placeholder="Zero Cool"
|
||||
class=(if this.isValidZoomDisplayName "valid" "") id="c-zoom-display-name"}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
{{input type="submit" disabled=inProgress
|
||||
value=(if inProgress "Processing" "Save")}}
|
||||
{{input type="submit" disabled=this.inProgress
|
||||
value=(if this.inProgress "Processing" "Save")}}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -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.');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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">
|
||||
{{ember-chart type="doughnut"
|
||||
data=chartData
|
||||
options=chartOptions
|
||||
width=200
|
||||
height=200}}
|
||||
<EmberChart @type="doughnut"
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@width="200" @height="200" />
|
||||
</div>
|
||||
@@ -24,12 +24,10 @@ export default Component.extend({
|
||||
contributors: sort('kredits.contributors', 'contributorsSorting'),
|
||||
|
||||
contributorsActive: computed('contributors.[]', 'contributions', function() {
|
||||
let activeIds = this.contributions.mapBy('contributorId')
|
||||
.map(id => id.toString())
|
||||
.uniq();
|
||||
return this.contributors.filter(c => {
|
||||
return activeIds.includes(c.id.toString());
|
||||
});
|
||||
const activeIds = new Set(this.contributions.mapBy('contributorId')
|
||||
.map(id => id.toString()));
|
||||
|
||||
return this.contributors.filter(c => activeIds.has(c.id.toString()));
|
||||
}),
|
||||
|
||||
contributionKinds: computed('contributions.[]', function() {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
{{#if showQuickFilter}}
|
||||
{{#if @showQuickFilter}}
|
||||
<div class="quick-filter">
|
||||
<p>
|
||||
<label class="filter-contributor">
|
||||
Contributor:
|
||||
<select onchange={{action (mut contributorId) value="target.value"}}>
|
||||
<select onchange={{action (mut this.contributorId) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each contributorsActive as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{#each this.contributorsActive as |contributor|}}
|
||||
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-kind">
|
||||
Kind:
|
||||
<select onchange={{action (mut contributionKind) value="target.value"}}>
|
||||
<select onchange={{action (mut this.contributionKind) value="target.value"}}>
|
||||
<option value="" selected>all</option>
|
||||
{{#each contributionKinds as |kind|}}
|
||||
<option value={{kind}} selected={{eq contributionKind kind}}>{{capitalize-string kind}}</option>
|
||||
{{#each this.contributionKinds as |kind|}}
|
||||
<option value={{kind}} selected={{eq this.contributionKind kind}}>{{capitalize-string kind}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="filter-contribution-size">
|
||||
{{input type="checkbox" checked=hideSmallContributions}}
|
||||
<Input @type="checkbox" @checked={{this.hideSmallContributions}} />
|
||||
Hide small contributions
|
||||
</label>
|
||||
</p>
|
||||
@@ -30,12 +30,12 @@
|
||||
{{/if}}
|
||||
|
||||
<ul class="contribution-list">
|
||||
{{#each contributionsFiltered as |contribution|}}
|
||||
<li role="button" {{action "openContributionDetails" contribution}}
|
||||
data-contribution-id={{contribution.id}}
|
||||
class="{{contribution-status contribution}}{{if contribution.vetoed " vetoed"}}{{if (eq contribution.id selectedContributionId) " selected"}}">
|
||||
{{#each this.contributionsFiltered as |contribution|}}
|
||||
<li role="button" data-contribution-id={{contribution.id}}
|
||||
{{action "openContributionDetails" contribution}}
|
||||
class="{{contribution-status contribution}}{{if (eq contribution.id @selectedContributionId) " selected"}}">
|
||||
<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="title">{{contribution.description}}</span>
|
||||
</p>
|
||||
@@ -45,7 +45,9 @@
|
||||
{{#unless contribution.vetoed}}
|
||||
{{#unless (is-confirmed-contribution contribution)}}
|
||||
<p class="voting">
|
||||
<button {{action "veto" contribution.id}} class="small danger">veto</button>
|
||||
{{input type="button" class="button small danger" value="veto"
|
||||
click=(action "veto" contribution.id)
|
||||
disabled=contribution.hasPendingChanges}}
|
||||
</p>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<tbody>
|
||||
{{#each contributorList as |c|}}
|
||||
<tr role="button" {{action "openContributorDetails" c.contributor}}
|
||||
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}">
|
||||
{{#each @contributorList as |c|}}
|
||||
<tr role="button"
|
||||
onclick={{action "openContributorDetails" c.contributor}}
|
||||
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id @selectedContributorId) "selected"}}">
|
||||
<td class="person">
|
||||
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
|
||||
<UserAvatar @contributor={{c.contributor}} /> {{c.contributor.name}}
|
||||
</td>
|
||||
<td class="kredits">
|
||||
<span class="amount">
|
||||
{{#if showUnconfirmedKredits}}
|
||||
{{#if @showUnconfirmedKredits}}
|
||||
{{c.amountTotal}}
|
||||
{{else}}
|
||||
{{c.amountConfirmed}}
|
||||
|
||||
@@ -6,7 +6,7 @@ export default Component.extend({
|
||||
account: null,
|
||||
|
||||
iconComponentName: computed('account.site', function() {
|
||||
if (this.account.site.match(/github|gitea|wiki/)) {
|
||||
if (this.account.site.match(/github|gitea|wiki|zoom/)) {
|
||||
return 'icon-account-' + this.account.site.replace(/\./g, '-');
|
||||
} else {
|
||||
return 'icon-web-globe';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<a href={{account.url}} target="_blank" rel="noopener" title="{{account.username}} on {{account.site}}">
|
||||
<span class="site">{{account.site}}</span>
|
||||
{{component iconComponentName}}
|
||||
<a href={{this.account.url}} target="_blank" rel="noopener noreferrer"
|
||||
title="{{this.account.username}} on {{this.account.site}}">
|
||||
<span class="site">{{this.account.site}}</span>
|
||||
{{component this.iconComponentName}}
|
||||
</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: ""
|
||||
});
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -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}}
|
||||
@@ -0,0 +1,46 @@
|
||||
import Component from '@ember/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Component.extend({
|
||||
|
||||
tagName: '',
|
||||
|
||||
kredits: service(),
|
||||
router: service(),
|
||||
|
||||
setupInProgress: false,
|
||||
|
||||
userHasEthereumWallet: computed(function() {
|
||||
return isPresent(window.ethereum);
|
||||
}),
|
||||
|
||||
showConnectButton: computed('userHasEthereumWallet',
|
||||
'kredits.hasAccounts', function() {
|
||||
return this.userHasEthereumWallet &&
|
||||
!this.kredits.hasAccounts;
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
signup() {
|
||||
this.router.transitionTo('signup');
|
||||
},
|
||||
|
||||
async connectAccount() {
|
||||
try {
|
||||
await window.ethereum.enable();
|
||||
this.set('setupInProgress', true);
|
||||
await this.kredits.setup();
|
||||
this.set('setupInProgress', false);
|
||||
this.router.transitionTo('dashboard');
|
||||
} catch (error) {
|
||||
this.set('setupInProgress', false);
|
||||
console.log('Opening Ethereum wallet failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<section id="user-account">
|
||||
{{#if this.setupInProgress}}
|
||||
Connecting account...
|
||||
{{else}}
|
||||
{{#if (and this.kredits.hasAccounts this.kredits.currentUser)}}
|
||||
{{this.kredits.currentUser.name}}
|
||||
{{#if this.kredits.currentUserIsCore}}
|
||||
<span class="core-flag">(core)</span>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<span class="anonymous">Anonymous</span>
|
||||
<button onclick={{action "signup"}} class="small" type="button">Sign up</button>
|
||||
{{#if this.showConnectButton}}
|
||||
<button onclick={{action "connectAccount"}} class="small green" type="button">Connect account</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</section>
|
||||
@@ -24,6 +24,9 @@ export default Component.extend({
|
||||
|
||||
if (github_uid) {
|
||||
return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=${SIZES[this.size]}`;
|
||||
} else {
|
||||
// TODO use custom avatar
|
||||
return '';
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
queryParams: ['contributorId', 'kind', 'amount'],
|
||||
|
||||
contributors: alias('kredits.contributors'),
|
||||
minedContributors: filterBy('contributors', 'id'),
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import ContributionsNewController from 'kredits-web/controllers/contributions/new';
|
||||
|
||||
export default ContributionsNewController.extend({
|
||||
|
||||
attributes: null,
|
||||
|
||||
});
|
||||
@@ -8,9 +8,7 @@ export default Controller.extend({
|
||||
actions: {
|
||||
|
||||
save (attributes) {
|
||||
return this.kredits
|
||||
.updateContributor(this.model.id, attributes)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
return this.kredits.updateContributor(this.model.id, attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ export default Controller.extend({
|
||||
actions: {
|
||||
|
||||
save (contributor) {
|
||||
return this.kredits
|
||||
.addContributor(contributor)
|
||||
.then(() => this.transitionToRoute('index'))
|
||||
return this.kredits.addContributor(contributor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ export default Controller.extend({
|
||||
showQuickFilterUnconfirmed: false,
|
||||
showQuickFilterConfirmed: false,
|
||||
|
||||
showIntroText: computed('kredits.{hasAccounts,currentUser}', function(){
|
||||
return (!this.kredits.hasAccounts || !this.kredits.currentUser);
|
||||
}),
|
||||
|
||||
actions: {
|
||||
|
||||
vetoContribution (contributionId) {
|
||||
@@ -41,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 () {
|
||||
this.toggleProperty('showQuickFilterUnconfirmed');
|
||||
},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
@@ -7,6 +8,10 @@ export default Controller.extend({
|
||||
if (this.model.isCore) return 'Core Contributor';
|
||||
if (this.model.totalKreditsEarned <= 5000) return 'Newcomer';
|
||||
return 'Contributor';
|
||||
}),
|
||||
|
||||
ipfsGatewayUrl: computed(function() {
|
||||
return config.ipfs.gatewayUrl;
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not } from '@ember/object/computed';
|
||||
import { isAddress } from 'web3-utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
ethAddress: null,
|
||||
githubAccessToken: alias('kredits.githubAccessToken'),
|
||||
|
||||
isValidEthAccount: computed('ethAddress', function() {
|
||||
return isAddress(this.ethAddress);
|
||||
}),
|
||||
|
||||
signupButtonDisabled: not('isValidEthAccount'),
|
||||
|
||||
actions: {
|
||||
|
||||
completeSignup () {
|
||||
const payload = {
|
||||
accessToken: this.githubAccessToken,
|
||||
account: this.ethAddress
|
||||
}
|
||||
|
||||
fetch(config.githubSignupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
console.log('Created contributor:', data);
|
||||
|
||||
this.setProperties({
|
||||
githubAccessToken: null,
|
||||
ethAddress: null
|
||||
});
|
||||
|
||||
this.transitionToRoute('signup.complete');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import Controller from '@ember/controller';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
actions: {
|
||||
|
||||
connectGithub () {
|
||||
window.location = config.githubConnectUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import Helper from '@ember/component/helper';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { once } from '@ember/runloop';
|
||||
|
||||
export default Helper.extend({
|
||||
|
||||
@@ -8,13 +9,48 @@ export default Helper.extend({
|
||||
currentBlock: alias('kredits.currentBlock'),
|
||||
|
||||
compute([contribution]) {
|
||||
this.setupRecompute(contribution);
|
||||
|
||||
let status = [];
|
||||
|
||||
if (contribution.vetoed) {
|
||||
return 'vetoed';
|
||||
status.push('vetoed');
|
||||
} else if (contribution.confirmedAt > this.currentBlock) {
|
||||
return 'unconfirmed';
|
||||
status.push('unconfirmed');
|
||||
} else {
|
||||
return 'confirmed'
|
||||
status.push('confirmed');
|
||||
}
|
||||
|
||||
if (contribution.hasPendingChanges) {
|
||||
status.push('pending');
|
||||
}
|
||||
|
||||
return status.join(' ');
|
||||
},
|
||||
|
||||
destroy () {
|
||||
if (this.teardown) this.teardown();
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
setupRecompute (contribution) {
|
||||
if (this.teardown) this.teardown();
|
||||
|
||||
contribution.addObserver('vetoed' , this, this.triggerRecompute);
|
||||
contribution.addObserver('confirmedAt' , this, this.triggerRecompute);
|
||||
contribution.addObserver('currentBlock' , this, this.triggerRecompute);
|
||||
contribution.addObserver('hasPendingChanges' , this, this.triggerRecompute);
|
||||
|
||||
this.teardown = () => {
|
||||
contribution.removeObserver('vetoed', this, this.triggerRecompute);
|
||||
contribution.removeObserver('confirmedAt', this, this.triggerRecompute);
|
||||
contribution.removeObserver('currentBlock', this, this.triggerRecompute);
|
||||
contribution.removeObserver('hasPendingChanges', this, this.triggerRecompute);
|
||||
};
|
||||
},
|
||||
|
||||
triggerRecompute () {
|
||||
once(this, this.recompute);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import EmberObject, { computed } from '@ember/object';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
import bignumber from 'kredits-web/utils/cps/bignumber';
|
||||
import moment from 'moment';
|
||||
|
||||
export default EmberObject.extend({
|
||||
|
||||
@@ -22,13 +24,23 @@ export default EmberObject.extend({
|
||||
time: null,
|
||||
ipfsData: '',
|
||||
|
||||
pendingTx: null,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('details', {});
|
||||
if (isEmpty(this.details)) this.set('details', {});
|
||||
},
|
||||
|
||||
iso8601Date: computed('date', 'time', function() {
|
||||
return this.time ? `${this.date}T${this.time}` : this.date;
|
||||
}),
|
||||
|
||||
jsDate: computed('iso8601Date', function() {
|
||||
return moment(this.iso8601Date).toDate();
|
||||
}),
|
||||
|
||||
hasPendingChanges: computed('pendingTx', function() {
|
||||
return isPresent(this.pendingTx);
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ export default EmberObject.extend({
|
||||
github_username: null,
|
||||
github_uid: null,
|
||||
wiki_username: null,
|
||||
zoom_display_name: null,
|
||||
ipfsData: ''
|
||||
|
||||
});
|
||||
|
||||
@@ -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', {});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import Resolver from 'ember-resolver';
|
||||
|
||||
export default Resolver;
|
||||
@@ -1,10 +1,10 @@
|
||||
import EmberRouter from '@ember/routing/router';
|
||||
import config from './config/environment';
|
||||
|
||||
const Router = EmberRouter.extend({
|
||||
location: config.locationType,
|
||||
rootURL: config.rootURL
|
||||
});
|
||||
export default class Router extends EmberRouter {
|
||||
location = config.locationType;
|
||||
rootURL = config.rootURL;
|
||||
}
|
||||
|
||||
Router.map(function() {
|
||||
this.route('dashboard', function() {
|
||||
@@ -16,16 +16,17 @@ Router.map(function() {
|
||||
this.route('show', { path: ':id' });
|
||||
});
|
||||
});
|
||||
this.route('proposals', function() {
|
||||
this.route('new');
|
||||
});
|
||||
this.route('contributions', function() {
|
||||
this.route('new');
|
||||
this.route('new', { queryParams: ['contributorId', 'kind', 'amount'] });
|
||||
this.route('resubmit', { path: ':id/resubmit' });
|
||||
});
|
||||
this.route('contributors', function() {
|
||||
this.route('new');
|
||||
this.route('edit', { path: ':id/edit' });
|
||||
});
|
||||
this.route('signup', function() {
|
||||
this.route('github');
|
||||
this.route('eth-account');
|
||||
this.route('complete');
|
||||
});
|
||||
});
|
||||
|
||||
export default Router;
|
||||
|
||||
@@ -4,7 +4,7 @@ import Route from '@ember/routing/route';
|
||||
export default Route.extend({
|
||||
kredits: service(),
|
||||
|
||||
beforeModel(transition) {
|
||||
beforeModel(/* transition */) {
|
||||
const kredits = this.kredits;
|
||||
|
||||
return kredits.setup().then(() => {
|
||||
@@ -12,11 +12,6 @@ export default Route.extend({
|
||||
console.error('Kredits preflight check failed!');
|
||||
console.error(error);
|
||||
});
|
||||
if (kredits.get('accountNeedsUnlock')) {
|
||||
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
|
||||
transition.retry();
|
||||
}
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log('Error initializing Kredits', error);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
model (params) {
|
||||
return { params };
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
model(params) {
|
||||
const contribution = this.kredits.contributions.findBy('id', parseInt(params.id));
|
||||
contribution.contributorId = contribution.contributorId.toString();
|
||||
|
||||
return contribution;
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
this._super(controller, model);
|
||||
|
||||
controller.set('attributes', model.getProperties([
|
||||
'kind', 'amount', 'description', 'url', 'details'
|
||||
]));
|
||||
controller.set('attributes.contributorId', model.contributorId.toString());
|
||||
controller.set('attributes.date', model.jsDate);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -22,7 +22,8 @@ export default Route.extend({
|
||||
github_username: model.github_username,
|
||||
github_uid: model.github_uid,
|
||||
gitea_username: model.gitea_username,
|
||||
wiki_username: model.wiki_username
|
||||
wiki_username: model.wiki_username,
|
||||
zoom_display_name: model.zoom_display_name
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
if (isEmpty(this.kredits.githubAccessToken)) {
|
||||
this.transitionTo('signup.index');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default Route.extend({
|
||||
|
||||
kredits: service(),
|
||||
|
||||
redirect () {
|
||||
this._super(...arguments);
|
||||
|
||||
let accessToken;
|
||||
try {
|
||||
accessToken = window.location.hash.match(/access_token=(.+)/)[1];
|
||||
} catch (error) { /* ignore */ }
|
||||
|
||||
if (isEmpty(accessToken) || accessToken === 'undefined') {
|
||||
console.error('No GitHub access token found.');
|
||||
this.transitionTo('signup');
|
||||
return;
|
||||
}
|
||||
|
||||
this.kredits.set('githubAccessToken', accessToken);
|
||||
|
||||
this.transitionTo('signup.eth-account');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -6,14 +6,13 @@ import Service from '@ember/service';
|
||||
import EmberObject from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, notEmpty } from '@ember/object/computed';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
|
||||
import groupBy from 'kredits-web/utils/group-by';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
|
||||
import config from 'kredits-web/config/environment';
|
||||
import Contributor from 'kredits-web/models/contributor'
|
||||
import Proposal from 'kredits-web/models/proposal'
|
||||
import Contribution from 'kredits-web/models/contribution'
|
||||
|
||||
export default Service.extend({
|
||||
@@ -23,16 +22,12 @@ export default Service.extend({
|
||||
currentUser: null,
|
||||
contributors: null,
|
||||
contributions: null,
|
||||
proposals: null,
|
||||
githubAccessToken: null,
|
||||
|
||||
currentUserIsContributor: notEmpty('currentUser'),
|
||||
currentUserIsCore: alias('currentUser.isCore'),
|
||||
hasAccounts: notEmpty('currentUserAccounts'),
|
||||
|
||||
accountNeedsUnlock: computed('currentUserAccounts', function() {
|
||||
return this.currentUserAccounts && isEmpty(this.currentUserAccounts);
|
||||
}),
|
||||
|
||||
contributionsUnconfirmed: computed('contributions.[]', 'currentBlock', function() {
|
||||
return this.contributions.filter(contribution => {
|
||||
return contribution.confirmedAt > this.currentBlock;
|
||||
@@ -80,7 +75,6 @@ export default Service.extend({
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('contributors', []);
|
||||
this.set('proposals', []);
|
||||
this.set('contributions', []);
|
||||
},
|
||||
|
||||
@@ -104,7 +98,14 @@ export default Service.extend({
|
||||
async function instantiateWithAccount (web3Provider, context) {
|
||||
console.debug('[kredits] Using user-provided instance, e.g. from Mist browser or Metamask');
|
||||
ethProvider = new ethers.providers.Web3Provider(web3Provider);
|
||||
// const network = await ethProvider.getNetwork();
|
||||
|
||||
const network = await ethProvider.getNetwork();
|
||||
if (isPresent(config.web3RequiredNetwork) &&
|
||||
network.name !== config.web3RequiredNetwork) {
|
||||
window.alert(`Please switch your Ethereum wallet to the "${config.web3RequiredNetwork}" network before connecting your account.`);
|
||||
return instantiateWithoutAccount();
|
||||
}
|
||||
|
||||
ethProvider.listAccounts().then(accounts => {
|
||||
context.set('currentUserAccounts', accounts);
|
||||
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
|
||||
@@ -116,12 +117,9 @@ export default Service.extend({
|
||||
}
|
||||
|
||||
if (window.ethereum) {
|
||||
try {
|
||||
// Request account access if needed
|
||||
await window.ethereum.enable();
|
||||
// Acccounts now exposed
|
||||
if (window.ethereum.isConnected()) {
|
||||
instantiateWithAccount(window.ethereum, this);
|
||||
} catch (error) {
|
||||
} else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
}
|
||||
@@ -209,8 +207,8 @@ export default Service.extend({
|
||||
|
||||
getContributors () {
|
||||
return this.kredits.Contributor.all()
|
||||
.then((contributors) => {
|
||||
return contributors.map((contributor) => {
|
||||
.then(contributors => {
|
||||
return contributors.map(contributor => {
|
||||
return Contributor.create(contributor);
|
||||
});
|
||||
});
|
||||
@@ -224,34 +222,13 @@ export default Service.extend({
|
||||
console.debug('[kredits] add contribution response', data);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId);
|
||||
const contribution = Contribution.create(attributes);
|
||||
// TODO receive from wrapper
|
||||
contribution.set('pendingTx', data);
|
||||
contribution.set('confirmedAtBlock', data.blockNumber + 40320);
|
||||
this.contributions.pushObject(contribution);
|
||||
return contribution;
|
||||
});
|
||||
},
|
||||
|
||||
addProposal (attributes) {
|
||||
console.debug('[kredits] add proposal', attributes);
|
||||
|
||||
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 => {
|
||||
@@ -262,32 +239,24 @@ export default Service.extend({
|
||||
});
|
||||
},
|
||||
|
||||
vote (proposalId) {
|
||||
console.debug('[kredits] vote for', proposalId);
|
||||
|
||||
return this.kredits.Proposal.functions.vote(proposalId)
|
||||
.then(data => {
|
||||
console.debug('[kredits] vote response', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
veto (contributionId) {
|
||||
console.debug('[kredits] veto against', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
|
||||
return this.kredits.Contribution.functions.veto(contributionId, { gasLimit: 300000 })
|
||||
.then(data => {
|
||||
console.debug('[kredits] veto response', data);
|
||||
contribution.set('pendingTx', data);
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentUser: computed('kredits.provider', function() {
|
||||
getCurrentUser: computed('kredits.provider', 'currentUserAccounts.[]', function() {
|
||||
if (isEmpty(this.currentUserAccounts)) {
|
||||
return RSVP.resolve();
|
||||
}
|
||||
return this.kredits.Contributor
|
||||
.functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject'))
|
||||
.functions.getContributorIdByAddress(this.currentUserAccounts.firstObject)
|
||||
.then((id) => {
|
||||
// check if the user is a contributor or not
|
||||
if (id === 0) {
|
||||
@@ -298,10 +267,6 @@ export default Service.extend({
|
||||
});
|
||||
}),
|
||||
|
||||
findProposalById(proposalId) {
|
||||
return this.proposals.findBy('id', proposalId.toString());
|
||||
},
|
||||
|
||||
// Contract events
|
||||
addContractEventHandlers () {
|
||||
this.kredits.Contributor
|
||||
@@ -310,13 +275,9 @@ export default Service.extend({
|
||||
.on('ContributorAdded', this.handleContributorChange.bind(this))
|
||||
|
||||
this.kredits.Contribution
|
||||
.on('ContributionAdded', this.handleContributionAdded.bind(this))
|
||||
.on('ContributionVetoed', this.handleContributionVetoed.bind(this))
|
||||
|
||||
this.kredits.Proposal
|
||||
.on('ProposalCreated', this.handleProposalCreated.bind(this))
|
||||
.on('ProposalVoted', this.handleProposalVoted.bind(this))
|
||||
.on('ProposalExecuted', this.handleProposalExecuted.bind(this));
|
||||
|
||||
this.kredits.Token
|
||||
.on('Transfer', this.handleTransfer.bind(this));
|
||||
},
|
||||
@@ -337,6 +298,24 @@ export default Service.extend({
|
||||
this.contributors.pushObject(newContributor);
|
||||
},
|
||||
|
||||
async handleContributionAdded (id, contributorId, amount) {
|
||||
console.debug('[kredits] ContributionAdded event received', { id, contributorId, amount });
|
||||
|
||||
const pendingContribution = this.contributions.find(c => {
|
||||
return (c.id === null) &&
|
||||
(c.contributorId.toString() === contributorId.toString()) &&
|
||||
(c.amount.toString() === amount.toString());
|
||||
});
|
||||
|
||||
if (pendingContribution) {
|
||||
const attributes = await this.kredits.Contribution.getById(id);
|
||||
attributes.contributor = this.contributors.findBy('id', attributes.contributorId.toString());
|
||||
const newContribution = Contribution.create(attributes);
|
||||
this.contributions.addObject(newContribution);
|
||||
this.contributions.removeObject(pendingContribution);
|
||||
}
|
||||
},
|
||||
|
||||
handleContributionVetoed (contributionId) {
|
||||
console.debug('[kredits] ContributionVetoed event received for ', contributionId);
|
||||
const contribution = this.contributions.findBy('id', contributionId);
|
||||
@@ -344,48 +323,10 @@ export default Service.extend({
|
||||
|
||||
if (contribution) {
|
||||
contribution.set('vetoed', true);
|
||||
contribution.set('pendingTx', null);
|
||||
}
|
||||
},
|
||||
|
||||
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) {
|
||||
value = value.toNumber();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ button, input[type=submit], .button {
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.1em;
|
||||
-webkit-appearance: none;
|
||||
|
||||
&+button, &+input[type=submit], &+.button {
|
||||
margin-left: 0.5rem;
|
||||
@@ -23,12 +24,16 @@ button, input[type=submit], .button {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
color: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.86rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
&.danger:not(:disabled) {
|
||||
color: $red;
|
||||
background-color: rgba(40, 21, 21, 0.6);
|
||||
border-color: rgba(40, 21, 21, 1);
|
||||
@@ -41,7 +46,7 @@ button, input[type=submit], .button {
|
||||
}
|
||||
}
|
||||
|
||||
&.green {
|
||||
&.green:not(:disabled) {
|
||||
color: $green;
|
||||
background-color: rgba(21, 40, 21, 0.6);
|
||||
border-color: rgba(21, 40, 21, 1);
|
||||
@@ -53,4 +58,17 @@ button, input[type=submit], .button {
|
||||
border-color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
&.icon {
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
vertical-align: middle;
|
||||
margin-right: 1rem;
|
||||
|
||||
.fg {
|
||||
fill: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,3 @@ $red: #fb6868;
|
||||
$primary-color: $blue;
|
||||
|
||||
$body-text-color: #fff;
|
||||
|
||||
#topbar {
|
||||
background-color: rgba(255, 0, 255, .2);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
section#add-contributor,
|
||||
section#add-contribution,
|
||||
section#add-proposal {
|
||||
section#signup {
|
||||
|
||||
form {
|
||||
|
||||
p {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.label {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,71 @@ main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
.content {
|
||||
a {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
&.text-lg {
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#intro {
|
||||
padding: 2rem;
|
||||
background-color: rgba(0,0,0,.2);
|
||||
font-size: 1.6rem;
|
||||
|
||||
@include media-max(small) {
|
||||
padding: 2rem 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: inherit;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
line-height: 1.5em;
|
||||
font-size: 1.2rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 550px) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
*:hover, *:active, *:focus {
|
||||
@@ -17,13 +16,17 @@
|
||||
|
||||
$font-family-sans: 'Open Sans', sans-serif;
|
||||
|
||||
body {
|
||||
html, body {
|
||||
font-family: $font-family-sans;
|
||||
font-weight: 300;
|
||||
font-size: 12px;
|
||||
color: $body-text-color;
|
||||
}
|
||||
|
||||
body {
|
||||
background-image: linear-gradient(to bottom, rgba(22, 21, 40, .4), rgba(0, 0, 0, .75)), url('/img/bg.jpg');
|
||||
background-repeat: none;
|
||||
background-attachment: fixed;
|
||||
color: $body-text-color;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
@@ -38,6 +41,10 @@ h1, h2, h3, h4, h5, input, button {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
section {
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
@@ -69,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) {
|
||||
@@ -104,6 +95,6 @@ section {
|
||||
@import "components/contributor-profile";
|
||||
@import "components/external-account-link";
|
||||
@import "components/loading-spinner";
|
||||
@import "components/proposal-list";
|
||||
@import "components/topbar";
|
||||
@import "components/topbar-account-panel";
|
||||
@import "components/user-avatar";
|
||||
|
||||
@@ -29,7 +29,6 @@ section#contribution-details {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
&.who-what-when {
|
||||
@@ -42,12 +41,11 @@ section#contribution-details {
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
a:not(.button) {
|
||||
color: $primary-color;
|
||||
text-decoration: none;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
&:not(.button):hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
@@ -56,4 +54,47 @@ section#contribution-details {
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&.vetoed {
|
||||
.content {
|
||||
h3 {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hint.vetoed {
|
||||
overflow: auto;
|
||||
margin-top: 2rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
font-size: 1.2rem;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,10 @@ main section {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
p {
|
||||
font-size: inherit;
|
||||
padding: 0.2rem 0 0;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: inherit;
|
||||
|
||||
&+ label {
|
||||
margin-left: 3.6rem;
|
||||
}
|
||||
@@ -60,7 +57,6 @@ ul.contribution-list {
|
||||
p {
|
||||
align-self: center;
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -72,11 +68,8 @@ ul.contribution-list {
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
|
||||
&.title {
|
||||
a {
|
||||
font-size: inherit;
|
||||
text-decoration: none;
|
||||
color: $body-text-color;
|
||||
}
|
||||
@@ -122,39 +115,3 @@ ul.contribution-list {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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 {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ section#contributor-profile {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -1,114 +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;
|
||||
font-size: inherit;
|
||||
line-height: 2rem;
|
||||
|
||||
&.kredits-amount, &.voting {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.description {
|
||||
grid-column-start: span 2;
|
||||
}
|
||||
|
||||
&.voting {
|
||||
grid-column-start: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.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 {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
header#topbar section#user-account {
|
||||
|
||||
button {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
button + button {
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
@include media-max(small) {
|
||||
.anonymous {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{{loading-spinner}}
|
||||
<LoadingSpinner />
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
<header id="topbar">
|
||||
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1>
|
||||
|
||||
<section id="user-account">
|
||||
{{#if kredits.hasAccounts }}
|
||||
{{#if kredits.currentUser}}
|
||||
{{kredits.currentUser.name}}
|
||||
{{#if kredits.currentUserIsCore}}(core){{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
Anonymous
|
||||
{{/if}}
|
||||
</section>
|
||||
<h1><LinkTo @route="dashboard">Kosmos Kredits</LinkTo></h1>
|
||||
<TopbarAccountPanel />
|
||||
</header>
|
||||
|
||||
{{outlet}}
|
||||
|
||||
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg id="svg825" class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<svg class="icon-github" width="260" height="260" enable-background="new 0 0 212.4575 186.23" version="1.1" viewBox="0 0 260 260" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(23.766 36.885)">
|
||||
<path d="m151.79 86.234c-7.078 0.1229-14.154 0.5605-21.232 0.7069-12.318 0.248-24.64 0.5664-36.961 0.512-11.176-0.0472-22.358-0.9728-33.518-0.7052-7.3164 0.1776-14.373 2.2948-20.131 7.584-9.9472 9.1368-12.522 21-11.314 33.807 1.92 20.357 9.2716 31.449 29.115 37.064 15.793 4.4704 31.963 5.2088 48.199 4.9744 5.254 0 10.51 0.1367 15.758-0.0292 12.723-0.4064 25.264-2.2072 37.358-6.3596 10.846-3.7225 18.799-10.691 22.572-22.166 2.6736-8.1288 3.7148-16.388 2.5544-24.853-1.7772-12.959-13.275-30.86-32.4-30.535zm-81.576 59.996c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20zm76 0c-8.836 0-16-8.9551-16-20 0-11.045 7.164-20 16-20s16 8.9553 16 20c0 11.045-7.164 20-16 20z" fill="none"/>
|
||||
<path class="fg" d="m200.25 59.188c-2.5076-3.4493-4.3632-6.3888-3.8748-11.44 0.6796-7.0664-0.3536-14.396-1.3204-21.518-1.2188-8.9843-3.5236-17.74-7.1776-26.23-9.0332 1.9356-17.981 3.49-25.963 8.0976-8.4648 4.8908-16.695 10.215-25.08 15.25-0.9532 0.5704-2.3124 0.8848-3.3848 0.7032-17.967-3.1368-35.92-3.0196-53.898-0.086-1.3632 0.2244-3.0724-0.2345-4.2948-0.9608-4.4436-2.6604-8.6564-5.7344-13.123-8.3516-11.656-6.844-23.66-12.822-37.494-14.59-0.3928 0.8597-0.7188 1.4785-0.9612 2.1253-4.164 11.22-6.6424 22.844-7.4276 34.82-0.2872 4.3516-0.6092 8.8048 0 13.072 0.3456 2.4296-4e-3 3.8788-1.3924 5.6132-4.8516 6.0488-8.6368 12.717-11.102 20.219-4.6972 14.295-4.4004 28.861-2.2756 43.475 1.7268 11.974 4.7344 23.537 11.324 33.873 13.186 20.682 32.892 29.774 55.572 31.705 19.308 1.6464 38.8 1.1992 58.213 1.2364 13.855 0.0251 27.57-1.4924 40.715-6.6389 20.375-7.9727 33.859-22.604 40.275-44.166 3.7344-12.545 5.2324-25.396 4.8144-38.498-0.4412-13.81-4.0504-26.566-12.144-37.711zm-18.609 82.435c-3.7736 11.475-11.727 18.444-22.572 22.166-12.094 4.1524-24.635 5.9532-37.358 6.3596-5.248 0.1659-10.504 0.0292-15.758 0.0292-16.236 0.2344-32.406-0.504-48.199-4.9744-19.844-5.6152-27.195-16.707-29.115-37.064-1.2072-12.807 1.3672-24.67 11.314-33.807 5.7576-5.2892 12.814-7.4064 20.131-7.584 11.16-0.2676 22.342 0.658 33.518 0.7052 12.32 0.0544 24.642-0.264 36.961-0.512 7.0784-0.1464 14.154-0.584 21.232-0.7069 19.125-0.3243 30.623 17.576 32.4 30.535 1.1604 8.4647 0.1192 16.724-2.5544 24.853z"/>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 703 B After Width: | Height: | Size: 703 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="256" height="256" version="1.1" viewBox="0 0 67.733 67.733" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g transform="translate(0 -229.27)">
|
||||
<g transform="matrix(.90278 0 0 .90278 6.5684 141.05)" fill-rule="evenodd">
|
||||
<path class="fg" d="m32.67 117.1c2.2056 0.1405 3.0528 0.27041 4.1992 0.64479 3.9359 1.2854 6.7418 4.6736 7.3059 8.8223 0.06615 0.48578 0.13996 1.0253 0.16431 1.1986 0.29104 2.0743 0.07488 21.552-0.25003 22.55-0.59082 1.814-1.901 2.7993-4.0537 3.0488-1.3671 0.15849-30.564 0.0701-31.846-0.0963-5.2843-0.68659-8.8934-4.001-9.7398-8.9445-0.38418-2.2439-0.62362-20.748-0.29871-23.084 0.34422-2.4746 1.7264-3.797 4.3212-4.1338 0.98769-0.12832 28.191-0.13361 30.197-6e-3"/>
|
||||
<path class="fg" d="m61.114 118.11c0.56621 0.4072 0.97816 1.0565 1.1885 1.874 0.19553 0.75909 0.24474 28.04 0.05371 29.747-0.37333 3.3361-2.6305 4.0137-5.5264 1.6584-7.9722-6.4841-9.021-7.5734-9.6795-10.052-0.26008-0.97895-0.33152-10.629-0.08811-11.876 0.38021-1.9457 1.2594-3.1946 3.7383-5.3099 7.4491-6.3564 7.5914-6.4569 9.0522-6.4008 0.70723 0.0272 0.8599 0.0709 1.2613 0.3593"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,15 @@
|
||||
<svg width="60px" height="60px" version="1.1" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<g transform="translate(-479,-238)" stroke="#fbe468" stroke-linecap="round" stroke-linejoin="round" stroke-width="1">
|
||||
<g transform="translate(482,244)">
|
||||
<a transform="translate(0,2)">
|
||||
<polygon id="Triangle-58" points="0.15321 44 27 0 53.847 44"/>
|
||||
</a>
|
||||
<rect x="25" y="13" width="4" height="21"/>
|
||||
<a transform="translate(0,2)">
|
||||
<circle cx="27" cy="38" r="2"/>
|
||||
</a>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 750 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -6,7 +6,9 @@
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contribution contributors=sortedContributors save=(action "save")}}
|
||||
<AddContribution @contributors={{this.sortedContributors}}
|
||||
@attributes={{this.model.params}}
|
||||
@save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<main class="center-column">
|
||||
|
||||
<section id="add-contribution">
|
||||
<header>
|
||||
<h2>Re-submit contribution #{{this.model.id}}</h2>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<AddContribution @attributes={{this.attributes}}
|
||||
@contributors={{this.sortedContributors}}
|
||||
@save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
@@ -6,7 +6,7 @@
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor attributes=attributes save=(action "save")}}
|
||||
<AddContributor @attributes={{this.attributes}} @save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
{{add-contributor save=(action "save")}}
|
||||
<AddContributor @save={{action "save"}} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -1,26 +1,44 @@
|
||||
<main id="dashboard" class={{if showDetailsPane "with-details"}}>
|
||||
{{#if this.showIntroText}}
|
||||
<div id="intro" class={{if this.showDetailsPane "with-details"}}>
|
||||
<h2>
|
||||
Welcome to the contribution dashboard of the
|
||||
<a href="https://kosmos.org" target="_blank" rel="noreferrer noopener">Kosmos</a> project!
|
||||
</h2>
|
||||
<p>
|
||||
If you want to learn more about what the numbers mean and how this works,
|
||||
check out the
|
||||
<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,
|
||||
<LinkTo @route="signup">create a contributor profile</LinkTo>.
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<main id="dashboard" class={{if this.showDetailsPane "with-details"}}>
|
||||
|
||||
<div id="stats">
|
||||
<section id="people">
|
||||
<header class="with-nav">
|
||||
<h2>Contributors</h2>
|
||||
{{#if kredits.hasAccounts}}
|
||||
{{#if this.kredits.hasAccounts}}
|
||||
<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>
|
||||
{{/if}}
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contributor-list contributorList=kreditsToplist
|
||||
showUnconfirmedKredits=showUnconfirmedKredits
|
||||
selectedContributorId=selectedContributorId}}
|
||||
<ContributorList @contributorList={{this.kreditsToplist}}
|
||||
@showUnconfirmedKredits={{this.showUnconfirmedKredits}}
|
||||
@selectedContributorId={{this.selectedContributorId}} />
|
||||
|
||||
<p class="stats">
|
||||
<span class="number">{{await kredits.totalKreditsEarned}}</span> kredits confirmed and issued to
|
||||
<span class="number">{{contributorsWithKredits.length}}</span> contributors
|
||||
<span class="number">{{await this.kredits.totalKreditsEarned}}</span> kredits confirmed and issued to
|
||||
<span class="number">{{this.contributorsWithKredits.length}}</span> contributors
|
||||
</p>
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
@@ -31,30 +49,36 @@
|
||||
<h2>Contributions by type</h2>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{chart-contributions-by-type contributions=contributions}}
|
||||
<ChartContributionsByType @contributions={{this.contributions}} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div id="contributions">
|
||||
{{#if contributionsUnconfirmed}}
|
||||
{{#if this.contributionsUnconfirmed}}
|
||||
<section id="contributions-unconfirmed">
|
||||
<header class="with-nav">
|
||||
<h2>Latest Contributions</h2>
|
||||
{{#if kredits.hasAccounts}}
|
||||
<nav>
|
||||
<button {{action "toggleQuickFilterUnconfirmed"}} class="small {{if showQuickFilterUnconfirmed "active"}}">filter</button>
|
||||
{{link-to "add" "contributions.new" title="Submit a contribution" class="button small green"}}
|
||||
</nav>
|
||||
<button type="button"
|
||||
onclick={{action "toggleQuickFilterUnconfirmed"}}
|
||||
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}}
|
||||
</nav>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{!-- TODO: We need a better naming for kredits.hasAccounts --}}
|
||||
{{contribution-list contributions=contributionsUnconfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
contractInteractionEnabled=kredits.hasAccounts
|
||||
selectedContributionId=selectedContributionId
|
||||
showQuickFilter=showQuickFilterUnconfirmed}}
|
||||
<ContributionList @contributions={{this.contributionsUnconfirmedSorted}}
|
||||
@vetoContribution={{action "vetoContribution"}}
|
||||
@contractInteractionEnabled={{this.kredits.hasAccounts}}
|
||||
@selectedContributionId={{this.selectedContributionId}}
|
||||
@showQuickFilter={{this.showQuickFilterUnconfirmed}} />
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
@@ -63,14 +87,18 @@
|
||||
<header class="with-nav">
|
||||
<h2>Confirmed Contributions</h2>
|
||||
<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>
|
||||
</header>
|
||||
<div class="content">
|
||||
{{contribution-list contributions=contributionsConfirmedSorted
|
||||
vetoContribution=(action "vetoContribution")
|
||||
selectedContributionId=selectedContributionId
|
||||
showQuickFilter=showQuickFilterConfirmed}}
|
||||
<ContributionList @contributions={{this.contributionsConfirmedSorted}}
|
||||
@vetoContribution={{action "vetoContribution"}}
|
||||
@selectedContributionId={{this.selectedContributionId}}
|
||||
@showQuickFilter={{this.showQuickFilterConfirmed}} />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<section id="contribution-details">
|
||||
<section id="contribution-details" class={{if this.model.vetoed "vetoed"}}>
|
||||
<header class="with-nav">
|
||||
<h2>Contribution #{{model.id}}</h2>
|
||||
<h2>Contribution #{{this.model.id}}</h2>
|
||||
<nav>
|
||||
<span class="amount">{{model.amount}}</span>
|
||||
<span class="amount">{{this.model.amount}}</span>
|
||||
<span class="symbol">₭S</span>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -10,35 +10,50 @@
|
||||
<div class="content">
|
||||
<p class="who-what-when">
|
||||
<span class="contributor">
|
||||
{{user-avatar contributor=model.contributor}}
|
||||
{{link-to model.contributor.name "dashboard.contributors.show" model.contributor}}
|
||||
<UserAvatar @contributor={{this.model.contributor}} />
|
||||
<LinkTo @route="dashboard.contributors.show" @model={{this.model.contributor}}>{{this.model.contributor.name}}</LinkTo>
|
||||
</span>
|
||||
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>
|
||||
<h3>{{model.description}}</h3>
|
||||
<h3>{{this.model.description}}</h3>
|
||||
<p>
|
||||
Kind: {{model.kind}}
|
||||
<br>Status: {{contribution-status model}}
|
||||
Kind: {{this.model.kind}}
|
||||
<br>Status: {{contribution-status this.model}}
|
||||
</p>
|
||||
{{#if model.url}}
|
||||
{{#if this.model.url}}
|
||||
<p>
|
||||
<a href={{model.url}}
|
||||
title={{model.description}}
|
||||
<a href={{this.model.url}}
|
||||
title={{this.model.description}}
|
||||
class="button"
|
||||
target="_blank"
|
||||
rel="noopener">
|
||||
rel="noopener noreferrer">
|
||||
Open URL
|
||||
</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{#if this.model.vetoed}}
|
||||
<div class="hint vetoed">
|
||||
<div class="icon">
|
||||
<IconWarning />
|
||||
</div>
|
||||
<p>
|
||||
This contribution has been vetoed, meaning no
|
||||
kredits will be issued.
|
||||
</p>
|
||||
<p>
|
||||
<LinkTo @route="contributions.resubmit" @model={{this.model}} class="button small green">Re-submit contribution …</LinkTo>
|
||||
</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<p>
|
||||
{{#if model.ipfsHash}}
|
||||
<a href="https://ipfs.io/ipfs/{{model.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener">
|
||||
<LinkTo @route="contributions.resubmit" @model={{this.model}} class="button small">Copy & edit as new</LinkTo>
|
||||
{{#if this.model.ipfsHash}}
|
||||
<a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener noreferrer">
|
||||
Inspect IPFS data
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
@@ -1,36 +1,41 @@
|
||||
<section id="contributor-profile">
|
||||
<header>
|
||||
{{user-avatar contributor=model size="medium"}}
|
||||
<UserAvatar @contributor={{this.model}} @size="medium" />
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<h2>{{model.name}}</h2>
|
||||
<p class="role">{{roleName}}</p>
|
||||
<h2>{{this.model.name}}</h2>
|
||||
<p class="role">{{this.roleName}}</p>
|
||||
|
||||
<ul class="external-accounts">
|
||||
{{#each model.accounts as |account|}}
|
||||
{{#each this.model.accounts as |account|}}
|
||||
<li>
|
||||
{{external-account-link account=account}}
|
||||
<ExternalAccountLink @account={{account}} />
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#if model.url}}
|
||||
{{#if this.model.url}}
|
||||
<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>
|
||||
{{icon-web-globe}}
|
||||
<IconWebGlobe />
|
||||
</a>
|
||||
</li>
|
||||
{{/if}}
|
||||
</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 class="actions">
|
||||
<p>
|
||||
<a href="https://rinkeby.etherscan.io/address/{{model.account}}" class="button small" target="_blank" rel="noopener">Inspect Ethereum transactions</a>
|
||||
{{#if model.ipfsHash}}
|
||||
<a href="https://ipfs.io/ipfs/{{model.ipfsHash}}" class="button small" target="_blank" rel="noopener">Inspect IPFS profile</a>
|
||||
<a href="https://rinkeby.etherscan.io/address/{{this.model.account}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect Ethereum transactions</a>
|
||||
{{#if this.model.ipfsHash}}
|
||||
<a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect IPFS profile</a>
|
||||
{{/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>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<main class="center-column">
|
||||
<section id="signup">
|
||||
{{outlet}}
|
||||
</section>
|
||||
</main>
|
||||
@@ -0,0 +1,15 @@
|
||||
<header>
|
||||
<h2>Welcome aboard!</h2>
|
||||
</header>
|
||||
<div class="content text-lg text-center">
|
||||
<p>
|
||||
Congratulations. Your initial profile is now complete.
|
||||
</p>
|
||||
<p>
|
||||
Why not say hi to your fellow contributors
|
||||
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved">in one of our chat rooms</a>?.
|
||||
</p>
|
||||
<p class="actions">
|
||||
<LinkTo @route="dashboard" class="button small">Return to dashboard</LinkTo>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<header>
|
||||
<h2>Complete your contributor profile</h2>
|
||||
</header>
|
||||
<div class="content text-lg">
|
||||
<p class="mg-bottom-md">
|
||||
Kredits allow you to take part in project governance, and to earn rewards for
|
||||
your contributions. For both, you will need an Ethereum wallet/account.
|
||||
</p>
|
||||
<form>
|
||||
<p>
|
||||
<label>
|
||||
Ethereum account:<br>
|
||||
<Input @type="text" @value={{this.ethAddress}} placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" class={{if this.isValidEthAccount "valid" ""}} />
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
<p class="actions">
|
||||
<button disabled={{this.signupButtonDisabled}}
|
||||
onclick={{action "completeSignup"}}
|
||||
type="button">
|
||||
Complete my profile
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
<header>
|
||||
<h2>Create your contributor profile</h2>
|
||||
</header>
|
||||
<div class="content text-lg">
|
||||
<p>
|
||||
You have already contributed to a Kosmos project, or you are interested
|
||||
in contributing in the near future? Fantastic!
|
||||
</p>
|
||||
<p>
|
||||
In order to earn kredits for your contributions, you will need a
|
||||
registered contributor profile. The quickest way to register is by
|
||||
connecting one of the following accounts:
|
||||
</p>
|
||||
<p class="actions">
|
||||
<button onclick={{action "connectGithub"}} class="icon" type="button">
|
||||
<IconAccountGithubCom />
|
||||
Connect GitHub
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -36,6 +36,10 @@ module.exports = function(environment) {
|
||||
},
|
||||
|
||||
web3ProviderUrl: 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9',
|
||||
web3RequiredNetwork: 'rinkeby',
|
||||
|
||||
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github',
|
||||
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github',
|
||||
|
||||
ipfs: {
|
||||
host: 'ipfs.kosmos.org',
|
||||
@@ -53,6 +57,9 @@ module.exports = function(environment) {
|
||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||
ENV.web3ProviderUrl = 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9';
|
||||
|
||||
ENV.githubConnectUrl = 'http://localhost:8888/kredits/signup/connect/github';
|
||||
ENV.githubSignupUrl = 'http://localhost:8888/kredits/signup/github';
|
||||
|
||||
ENV.ipfs = {
|
||||
host: 'localhost',
|
||||
port: '5001',
|
||||
@@ -73,16 +80,12 @@ module.exports = function(environment) {
|
||||
ENV.APP.autoboot = false;
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
// here you can enable a production-specific feature
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN || 'open.aragonpm.eth';
|
||||
}
|
||||
|
||||
if (process.env.WEB3_PROVIDER_URL) {
|
||||
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||
ENV.web3RequiredNetwork = null;
|
||||
}
|
||||
if (process.env.KREDITS_KERNEL_ADDRESS) {
|
||||
ENV.kreditsKernelAddress = process.env.KREDITS_KERNEL_ADDRESS;
|
||||
if (process.env.KREDITS_DAO_ADDRESS) {
|
||||
ENV.kreditsKernelAddress = process.env.KREDITS_DAO_ADDRESS;
|
||||
}
|
||||
if (process.env.KREDITS_APM_DOMAIN) {
|
||||
ENV.kreditsApmDomain = process.env.KREDITS_APM_DOMAIN;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
{
|
||||
"jquery-integration": true
|
||||
"application-template-wrapper": false,
|
||||
"default-async-observers": true,
|
||||
"jquery-integration": false,
|
||||
"template-only-glimmer-components": true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"version": "1.7.0",
|
||||
"version": "1.16.0",
|
||||
"private": true,
|
||||
"description": "Contribution dashboard of the Kosmos project",
|
||||
"repository": "https://github.com/67P/kredits-web",
|
||||
@@ -11,12 +11,14 @@
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*",
|
||||
"lint:hbs": "ember-template-lint .",
|
||||
"lint:js": "eslint .",
|
||||
"test": "ember test",
|
||||
"start": "KREDITS_APM_DOMAIN=open.aragonpm.eth ember serve",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember s",
|
||||
"build": "ember build",
|
||||
"start": "ember serve",
|
||||
"test": "npm-run-all lint:* test:*",
|
||||
"test:ember": "ember test",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:7545 ember serve",
|
||||
"build": "ember build --environment=production",
|
||||
"build-prod": "rm -rf release/* && ember build -prod --output-path release",
|
||||
"preversion": "npm test",
|
||||
"version": "npm run build-prod && git add release/",
|
||||
@@ -24,52 +26,59 @@
|
||||
"deploy": "git push 5apps master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ember/jquery": "^0.6.0",
|
||||
"@ember/optional-features": "^0.7.0",
|
||||
"@ember/optional-features": "^1.3.0",
|
||||
"@glimmer/component": "^1.0.0",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"babelify": "^7.3.0",
|
||||
"brfs": "^2.0.2",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"ember-ajax": "^5.0.0",
|
||||
"ember-auto-import": "^1.5.2",
|
||||
"ember-auto-import": "^1.5.3",
|
||||
"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-babel": "^7.7.3",
|
||||
"ember-cli-chart": "^3.4.0",
|
||||
"ember-cli-dependency-checker": "^3.1.0",
|
||||
"ember-cli-eslint": "^5.1.0",
|
||||
"ember-cli-htmlbars": "^3.0.1",
|
||||
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
|
||||
"ember-cli-inject-live-reload": "^1.8.2",
|
||||
"ember-cli-babel": "^7.19.0",
|
||||
"ember-cli-chart": "^3.6.0",
|
||||
"ember-cli-dependency-checker": "^3.2.0",
|
||||
"ember-cli-htmlbars": "^4.3.1",
|
||||
"ember-cli-inject-live-reload": "^2.0.2",
|
||||
"ember-cli-moment-shim": "^3.7.1",
|
||||
"ember-cli-sass": "^7.2.0",
|
||||
"ember-cli-sass": "^10.0.1",
|
||||
"ember-cli-sri": "^2.1.1",
|
||||
"ember-cli-template-lint": "^1.0.0-beta.1",
|
||||
"ember-cli-uglify": "^2.1.0",
|
||||
"ember-export-application-global": "^2.0.0",
|
||||
"ember-flatpickr": "^2.14.0",
|
||||
"ember-load-initializers": "^2.0.0",
|
||||
"ember-cli-uglify": "^3.0.0",
|
||||
"ember-export-application-global": "^2.0.1",
|
||||
"ember-fetch": "^8.0.1",
|
||||
"ember-flatpickr": "^2.16.2",
|
||||
"ember-load-initializers": "^2.1.1",
|
||||
"ember-macro-helpers": "0.17.0",
|
||||
"ember-maybe-import-regenerator": "^0.1.6",
|
||||
"ember-moment": "^7.8.1",
|
||||
"ember-promise-helpers": "1.0.6",
|
||||
"ember-qunit": "^4.4.1",
|
||||
"ember-resolver": "^5.0.1",
|
||||
"ember-source": "~3.10.0",
|
||||
"ember-promise-helpers": "^1.0.9",
|
||||
"ember-qunit": "^4.6.0",
|
||||
"ember-resolver": "^8.0.0",
|
||||
"ember-source": "~3.18.0",
|
||||
"ember-template-lint": "^2.6.0",
|
||||
"ember-truth-helpers": "github:jmurphyau/ember-truth-helpers#31a14373a31f1f82c77537720549b47a95c28e5f",
|
||||
"eslint-plugin-ember": "^6.2.0",
|
||||
"eslint-plugin-node": "^9.0.1",
|
||||
"ethers": "^4.0.27",
|
||||
"kosmos-schemas": "^2.0.0",
|
||||
"kredits-contracts": "^5.4.0",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint-plugin-ember": "^8.4.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"ethers": "^4.0.47",
|
||||
"kosmos-schemas": "^2.1.0",
|
||||
"kredits-contracts": "^5.5.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"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.5",
|
||||
"transform-loader": "^0.2.4",
|
||||
"tv4": "^1.3.0"
|
||||
"tv4": "^1.3.0",
|
||||
"web3-utils": "^1.2.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8.* || >= 10.*"
|
||||
"node": "10.* || >= 12"
|
||||
},
|
||||
"ember": {
|
||||
"edition": "octane"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
|
||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_JQUERY_INTEGRATION%22%3Atrue%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.7.0%2Ba4ca37d8%22%7D%2C%22browserify%22%3A%7B%22tests%22%3Atrue%2C%22transform%22%3A%5B%5B%22babelify%22%2C%7B%22presets%22%3A%5B%22es2015%22%5D%2C%22global%22%3Atrue%7D%5D%5D%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frinkeby.infura.io%2Fv3%2Fd4f788b7a6584f7db2fc3c268d4d09e9%22%2C%22ipfs%22%3A%7B%22host%22%3A%22ipfs.kosmos.org%22%2C%22port%22%3A%225444%22%2C%22protocol%22%3A%22https%22%2C%22gatewayUrl%22%3A%22https%3A%2F%2Fipfs.kosmos.org%2Fipfs%22%7D%2C%22kreditsApmDomain%22%3A%22open.aragonpm.eth%22%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_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.16.0%2B41cc37d3%22%7D%2C%22browserify%22%3A%7B%22tests%22%3Atrue%2C%22transform%22%3A%5B%5B%22babelify%22%2C%7B%22presets%22%3A%5B%22es2015%22%5D%2C%22global%22%3Atrue%7D%5D%5D%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frinkeby.infura.io%2Fv3%2Fd4f788b7a6584f7db2fc3c268d4d09e9%22%2C%22web3RequiredNetwork%22%3A%22rinkeby%22%2C%22githubConnectUrl%22%3A%22https%3A%2F%2Fhal8000.chat.kosmos.org%2Fkredits%2Fsignup%2Fconnect%2Fgithub%22%2C%22githubSignupUrl%22%3A%22https%3A%2F%2Fhal8000.chat.kosmos.org%2Fkredits%2Fsignup%2Fgithub%22%2C%22ipfs%22%3A%7B%22host%22%3A%22ipfs.kosmos.org%22%2C%22port%22%3A%225444%22%2C%22protocol%22%3A%22https%22%2C%22gatewayUrl%22%3A%22https%3A%2F%2Fipfs.kosmos.org%2Fipfs%22%7D%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||
|
||||
<link integrity="" rel="stylesheet" href="/assets/vendor-179cf27a97ea5bbb0ce72403a401cc8c.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-e95be4cbe614c5aa4c7a891c6eedb028.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/vendor-ca96b2e19fc9f356e3dbad90c9f3f323.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-8440190dda6de1748075524fe140b4ee.css">
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<body>
|
||||
|
||||
|
||||
<script src="/assets/vendor-d84a577c9f0624a0b15861936fe740f2.js" integrity="sha256-o5SitqUpXKccldebvatDY/WvXTbEnAu/IA/TyvBxU0U= sha512-gDMabciAMuF89anreuu39lAYDOf6tXMMHxVRUxtqGpmhJdSAUQGFQ2MWTgE8mt+8GvGvPxw9QvnHuBJDK34YVw==" ></script>
|
||||
<script src="/assets/kredits-web-abcf1575c8eb056a923a583efdc2d00f.js" integrity="sha256-IEH1g/CviSkQ/8acQOgUqB1iPBlXgsJlGJqsAzs9iqg= sha512-znuWeezXPnfkekpebFEC/4uVT0DjHS52ORURS11H7+kCtgyMjLqvSS8it/GzLxSsameIUaoMsvctqhL8f+atMg==" ></script>
|
||||
<script src="/assets/vendor-5ebca0a713676bf484d729ec58e9d74b.js" integrity="sha256-K9BPrQzg0Nv83HSPUwk2n1hU8IOaceDf03EaU5LgblQ= sha512-stOJYOdJfiuzleqChwK9pNLCVMQwg9sZXUSboTsvtIixnRoQ285yS080rRX/GfcRd/eI0TEpPI0XKqvN56kzLA==" ></script>
|
||||
<script src="/assets/kredits-web-d376614e43d3edec456b90f7efdfedf4.js" integrity="sha256-ZUj9gy+521ioE0n+gKl53UrRqVALwY7YTOonD2SjZfg= sha512-0G4MUqxkZ+yazboR03+jfA9BRKoyKh2/IgG9iAVttTfVZRyMTHqiFnGlG00KY72H0y2jVamQtgY6UpLgAohGLg==" ></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||