Compare commits

...

39 Commits

Author SHA1 Message Date
basti 6b3666def9 1.8.0 2019-09-01 16:04:21 +02:00
basti 74c8e32cc6 Merge pull request #142 from 67P/feature/signup
Sign up via GitHub
2019-08-29 12:52:04 +02:00
basti 5dde978499 Merge branch 'master' into feature/signup 2019-08-29 12:46:00 +02:00
galfert 0625ee9d79 Handle undefined Github access token 2019-08-28 12:53:21 +02:00
basti dc5dfe19f2 Add success page to GitHub signup 2019-08-27 18:19:46 +02:00
galfert c518b7f565 Merge pull request #149 from 67P/chore/dao_address
Rename kernel address to DAO address
2019-08-27 15:12:45 +02:00
basti d13242b210 Rename kernel address to DAO address
We're making the naming consistent everwhere.
2019-08-27 15:00:18 +02:00
basti ada029586a Merge pull request #148 from 67P/doc/dev_network
Documentation for IPFS and Metamask
2019-08-26 18:53:24 +02:00
galfert 7bf5fa444a Add notice about Metamask when using local devchain 2019-08-26 18:09:38 +02:00
galfert d08d5f9e8d Add IPFS Gateway config to readme 2019-08-26 18:00:17 +02:00
basti a2bd3f76b8 1.7.0 2019-08-12 14:55:54 +02:00
basti a4ca37d882 Merge pull request #146 from 67P/feauture/141-time_picker
Add time input to contribution form
2019-08-12 14:52:07 +02:00
basti 143317d909 Merge pull request #147 from 67P/docs/readme
Fix npm run start:local instructions
2019-08-04 19:43:06 +02:00
fsmanuel d8bcc8ff67 Fix npm run start:local instructions 2019-08-04 19:36:09 +02:00
basti e4a39ec67c Fix bug with default date input
When the input isn't touched by the user, the date isn't stored as an
array on the property.
2019-08-02 16:58:12 +02:00
basti 5b47ba9e11 Import missing module 2019-08-02 16:58:01 +02:00
basti d8f565cd9e Use start of current hour as default contribution time
Co-Authored-By: Garret Alfert <alfert@wevelop.de>
2019-08-02 16:08:13 +02:00
basti a9480a09f1 Add time input to conribution form
After adding it as a separate field, I figured out that we should use
the same picker, because otherwise we'd have to keep the date in sync
depending on the time zone. Zones are too messy in that regard, and the
date is already normalized to UTC before creating the contribution
record.

closes #141
2019-07-31 13:20:39 +02:00
galfert 60ba019139 Fix HAL8000 URL
Wrong port number.
2019-07-26 19:02:41 +02:00
galfert 413bcddb1e Wire up signup via Github 2019-07-26 03:38:18 +02:00
basti c2273d1778 Merge pull request #144 from 67P/feature/validate_eth_addresses
Validate Ethereum addresses in input fields
2019-07-24 17:30:51 +02:00
basti 780afc9bc6 Fix wrong property name 2019-07-24 11:56:51 +02:00
basti a8ae2b0156 Validate Ethereum addresses in input fields 2019-07-22 17:29:31 +02:00
basti d6d2b1a61c Add signup completion page 2019-07-21 17:03:50 +02:00
basti 4c70c0d233 Basic signup view 2019-07-21 16:04:00 +02:00
basti 725d36bed6 Add signup button for unknown users 2019-07-18 19:43:39 +02:00
basti 0a71d7ad91 Add signup route 2019-07-18 19:31:03 +02:00
basti 6a176b5904 Fix empty name in account info
When the user brings Ethereum accounts, but none of them is known as
contributor, instead of "Anonymous" it just shows nothing. This fixes
the if condition in the template to eliminate the faulty case.
2019-07-18 19:27:53 +02:00
basti c7d046aa46 Move topbar account info to component, add tests
Adds a failing test for users with wallet, but no known contributor
account, where it currently shows no name at all.
2019-07-18 19:26:16 +02:00
basti aa9c3648c0 1.6.0 2019-07-17 17:41:49 +02:00
galfert 9c18593529 Merge pull request #140 from 67P/feature/contribution_details
Contribution details
2019-07-17 17:23:14 +02:00
basti 52fe7ff026 Remove commented CSS 2019-07-17 16:55:04 +02:00
basti b02967c5ef Basic contribution details
Adds a details pane for contribution records, similar to contributor
profiles.

Just the basic details for now. Can be extended with all kinds of
data, links, icons, etc. from here on.
2019-07-17 12:58:16 +02:00
basti 7236b2d2b3 Add basics for contribution details
Route, controller, links, selected property/styles, etc.
2019-07-16 15:35:35 +02:00
basti fd1ae0a612 Merge pull request #139 from 67P/feature/update_ember
Update dependencies
2019-07-15 20:07:02 +02:00
galfert 612b76300c Remove ember-cli-update dependency
All but one of the ~80 vulnerabilities reported by `npm audit` were from dependencies of `ember-cli-update`.
2019-07-15 19:17:56 +02:00
galfert 42fe01b349 Run codemods 2019-07-15 19:17:56 +02:00
galfert 80af572742 Run ember-cli-update --to 3.10.1 2019-07-15 19:17:56 +02:00
galfert d7a0817ac6 Update ember-cli-update 2019-07-15 19:17:56 +02:00
57 changed files with 7847 additions and 9325 deletions
+10 -2
View File
@@ -1,7 +1,7 @@
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 2017,
ecmaVersion: 2018,
sourceType: 'module'
},
globals: {
@@ -41,7 +41,15 @@ module.exports = {
env: {
browser: false,
node: true
}
},
plugins: ['node'],
rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
// add your custom rules and overrides for node files here
// this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off'
})
}
]
};
+2 -1
View File
@@ -4,6 +4,7 @@ module.exports = {
extends: 'recommended',
rules: {
'simple-unless': false
'simple-unless': false,
'no-nested-interactive': false
}
};
-3
View File
@@ -23,9 +23,6 @@ env:
# See https://git.io/vdao3 for details.
- JOBS=1
before_install:
- npm config set spin false
script:
- npm run lint:hbs
- npm run lint:js
+11 -5
View File
@@ -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,10 +94,15 @@ With IPFS and Ethereum/ganache running, you can now start this Ember app.
* `npm link kredits-contracts` - link the local `kredits-contracts` package
* `npm run start:local` - WEB3_PROVIDER_URL=http://localhost:7545 must be set for local settings
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO Kernel address.
If that is the case the DAO address must be provided as `KREDITS_KERNEL_ADDRESS` environment variable.
If you restart the devchain your network ID changes. So kredits-contracts does not find the correct DAO address.
If that is the case the DAO address must be provided as `KREDITS_DAO_ADDRESS` environment variable.
* `KREDITS_KERNEL_ADDRESS=<0xYOURADDRESS> npm run start:local` - find your address with the `npm run dao:address` command in the kredits-contracts repo.
* `npm run dao:address` in the kredits-contracts repo to find your address
* `KREDITS_DAO_ADDRESS=<0xYOURADDRESS> npm run start:local`
#### 4. Metamask network
Switch the network in Metamask to "Custom RPC" with the RPC URL `http://localhost:7545`.
#### IPFS
@@ -107,6 +112,7 @@ Install IPFS with your favorite package manager and run
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '["localhost:4200"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '["PUT", "GET", "POST"]'
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials '["true"]'
ipfs config Addresses.Gateway /ip4/127.0.0.1/tcp/8080
## Further Reading / Useful Links
+9 -4
View File
@@ -1,6 +1,7 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
import { and, notEmpty } from '@ember/object/computed';
import moment from 'moment';
export default Component.extend({
@@ -21,13 +22,13 @@ export default Component.extend({
init () {
this._super(...arguments);
this.set('defaultDate', new Date());
this.set('defaultDate', moment().startOf('hour').toDate());
// Default attributes used by reset
this.set('attributes', {
contributorId: null,
kind: null,
date: [new Date()],
date: this.defaultDate,
amount: null,
description: null,
url: null,
@@ -49,8 +50,12 @@ export default Component.extend({
}
const attributes = this.getProperties(Object.keys(this.attributes));
const [ date/* , time */ ] = attributes.date[0].toISOString().split('T');
attributes.date = date;
let dateInput = (attributes.date instanceof Array) ?
attributes.date[0] : attributes.date;
const [ date, time ] = dateInput.toISOString().split('T');
[ attributes.date, attributes.time ] = [ date, time ];
this.set('inProgress', true);
+64 -50
View File
@@ -1,54 +1,68 @@
<form {{action "submit" on="submit"}}>
<p>
<select required onchange={{action (mut contributorId) value="target.value"}}>
<option value="" selected disabled hidden>Contributor</option>
{{#each contributors as |contributor|}}
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
{{/each}}
</select>
</p>
<p>
<select required onchange={{action (mut kind) value="target.value"}}>
<option value="" selected disabled hidden>Kind</option>
<option value="community" selected={{eq kind "community"}}>Community</option>
<option value="design" selected={{eq kind "design"}}>Design</option>
<option value="dev" selected={{eq kind "dev"}}>Development</option>
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
<option value="special" selected={{eq kind "special"}}>Special</option>
</select>
</p>
<p>
{{ember-flatpickr
allowInput=false
altFormat="F j, Y"
altInput=true
altInputClass="date-alt"
date=date
dateFormat="Y-m-d"
defaultDate=defaultDate
maxDate=defaultDate
onChange=(action (mut date))
}}
</p>
<p>
{{input type="text"
placeholder="500"
value=amount
class=(if isValidAmount "valid" "")}}
</p>
<p>
{{input type="text"
placeholder="Description"
value=description
class=(if isValidDescription "valid" "")}}
</p>
<p>
{{input type="text"
placeholder="URL (optional)"
value=url
class=(if isValidUrl "valid" "")}}
</p>
<label>
<p class="label">Contributor:</p>
<p>
<select required onchange={{action (mut contributorId) value="target.value"}}>
<option value="" selected disabled hidden></option>
{{#each contributors as |contributor|}}
<option value={{contributor.id}} selected={{eq contributorId contributor.id}}>{{contributor.name}}</option>
{{/each}}
</select>
</p>
</label>
<label>
<p class="label">Kind:</p>
<p>
<select required onchange={{action (mut kind) value="target.value"}}>
<option value="" selected disabled hidden></option>
<option value="community" selected={{eq kind "community"}}>Community</option>
<option value="design" selected={{eq kind "design"}}>Design</option>
<option value="dev" selected={{eq kind "dev"}}>Development</option>
<option value="docs" selected={{eq kind "docs"}}>Documentation</option>
<option value="ops" selected={{eq kind "ops"}}>IT Operations</option>
<option value="special" selected={{eq kind "special"}}>Special</option>
</select>
</p>
</label>
<label>
<p class="label">Date:</p>
<p>
{{ember-flatpickr
date=date
defaultDate=defaultDate
maxDate=defaultDate
enableTime=true
time_24hr=true
onChange=(action (mut date))
}}
</p>
</label>
<label>
<p class="label">Amount:</p>
<p>
{{input type="text"
placeholder="500"
value=amount
class=(if isValidAmount "valid" "")}}
</p>
</label>
<label>
<p class="label">Description:</p>
<p>
{{input type="text"
value=description
class=(if isValidDescription "valid" "")}}
</p>
</label>
<label>
<p class="label">URL (optional):</p>
<p>
{{input type="text"
value=url
class=(if isValidUrl "valid" "")}}
</p>
</label>
<p class="actions">
{{input type="submit"
disabled=inProgress
+7 -2
View File
@@ -1,7 +1,9 @@
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({
@@ -9,14 +11,17 @@ export default Component.extend({
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'),
isValid: and(
'isValidAccount',
'isValidName',
@@ -6,9 +6,13 @@ import { inject as service } from '@ember/service';
export default Component.extend({
router: service(),
tagName: 'div',
classNames: ['contributions'],
selectedContribution: null,
showQuickFilter: false,
hideSmallContributions: false,
contributorId: null,
@@ -57,6 +61,10 @@ export default Component.extend({
} else {
window.alert('Only members can veto contributions. Please ask someone to set you up.');
}
},
openContributionDetails(contribution) {
this.router.transitionTo('dashboard.contributions.show', contribution);
}
}
@@ -31,17 +31,13 @@
<ul class="contribution-list">
{{#each contributionsFiltered as |contribution|}}
<li data-contribution-id={{contribution.id}} class="{{contribution-status contribution}} {{if contribution.vetoed "vetoed"}}">
<li role="button" {{action "openContributionDetails" contribution}}
data-contribution-id={{contribution.id}}
class="{{contribution-status contribution}}{{if contribution.vetoed " vetoed"}}{{if (eq contribution.id selectedContributionId) " selected"}}">
<p class="meta">
<span class="recipient">{{user-avatar contributor=contribution.contributor}}</span>
<span class="category {{contribution.kind}}">({{contribution.kind}})</span>
<span class="title">
{{#if contribution.url}}
<a href={{contribution.url}} target="_blank" rel="noopener" title={{contribution.description}}>{{contribution.description}}</a>
{{else}}
{{contribution.description}}
{{/if}}
</span>
<span class="title">{{contribution.description}}</span>
</p>
<p class="kredits-amount">
<span class="amount">{{contribution.amount}}</span><span class="symbol">₭S</span>
@@ -7,6 +7,7 @@ export default Component.extend({
tagName: 'table',
classNames: 'contributor-list',
selectedContributorId: null,
actions: {
+2 -3
View File
@@ -1,8 +1,7 @@
<tbody>
{{#each contributorList as |c|}}
<tr role="button"
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}"
{{action "openContributorDetails" c.contributor}}>
<tr role="button" {{action "openContributorDetails" c.contributor}}
class="{{if (is-current-user c.contributor) "current-user"}} {{if (eq c.contributor.id selectedContributorId) "selected"}}">
<td class="person">
{{user-avatar contributor=c.contributor}} {{c.contributor.name}}
</td>
@@ -0,0 +1,19 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
export default Component.extend({
tagName: '',
kredits: service(),
router: service(),
actions: {
signup() {
this.router.transitionTo('signup');
}
}
});
@@ -0,0 +1,11 @@
<section id="user-account">
{{#if (and kredits.hasAccounts kredits.currentUser)}}
{{kredits.currentUser.name}}
{{#if kredits.currentUserIsCore}}
<span class="core-flag">(core)</span>
{{/if}}
{{else}}
Anonymous
<button {{action "signup"}} class="small green">Sign up</button>
{{/if}}
</section>
+1
View File
@@ -8,6 +8,7 @@ export default Controller.extend({
showDetailsPane: false,
selectedContributorId: null,
selectedContributionId: null,
currentBlock: alias('kredits.currentBlock'),
+49
View File
@@ -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');
});
}
}
});
+14
View File
@@ -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;
}
}
});
+8 -2
View File
@@ -1,4 +1,4 @@
import EmberObject from '@ember/object';
import EmberObject, { computed } from '@ember/object';
import bignumber from 'kredits-web/utils/cps/bignumber';
export default EmberObject.extend({
@@ -18,11 +18,17 @@ export default EmberObject.extend({
description: null,
details: null,
url: null,
date: null,
time: null,
ipfsData: '',
init () {
this._super(...arguments);
this.set('details', {});
}
},
iso8601Date: computed('date', 'time', function() {
return this.time ? `${this.date}T${this.time}` : this.date;
})
});
+9
View File
@@ -11,6 +11,10 @@ Router.map(function() {
this.route('contributors', function() {
this.route('show', { path: ':id' });
});
this.route('contributions', function() {
this.route('show', { path: ':id' });
});
});
this.route('proposals', function() {
this.route('new');
@@ -22,6 +26,11 @@ Router.map(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;
@@ -0,0 +1,32 @@
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import { alias } from '@ember/object/computed';
export default Route.extend({
kredits: service(),
contributions: alias('kredits.contributions'),
model (params) {
return this.contributions.findBy('id', parseInt(params.id));
},
setupController (controller, model) {
this._super(controller, model);
this.controllerFor('dashboard')
.setProperties({
showDetailsPane: true,
selectedContributionId: model.id
});
},
deactivate () {
this.controllerFor('dashboard')
.setProperties({
showDetailsPane: false,
selectedContributionId: null
});
}
});
+4
View File
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default Route.extend({
});
+4
View File
@@ -0,0 +1,4 @@
import Route from '@ember/routing/route';
export default Route.extend({
});
+17
View File
@@ -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');
}
}
});
+29
View File
@@ -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');
}
});
+1
View File
@@ -24,6 +24,7 @@ export default Service.extend({
contributors: null,
contributions: null,
proposals: null,
githubAccessToken: null,
currentUserIsContributor: notEmpty('currentUser'),
currentUserIsCore: alias('currentUser.isCore'),
+17
View File
@@ -23,6 +23,10 @@ button, input[type=submit], .button {
border-color: $primary-color;
}
&[disabled] {
color: rgba(255,255,255,0.5);
}
&.small {
font-size: 0.8rem;
padding: 0.2rem 0.8rem;
@@ -53,4 +57,17 @@ button, input[type=submit], .button {
border-color: $green;
}
}
&.icon {
svg {
width: 2rem;
height: 2rem;
vertical-align: middle;
margin-right: 1rem;
.fg {
fill: $primary-color;
}
}
}
}
+10 -1
View File
@@ -1,12 +1,21 @@
section#add-contributor,
section#add-contribution,
section#add-proposal {
section#add-proposal,
section#signup {
form {
p {
margin-bottom: 1.5rem;
&.mg-bottom-md {
margin-bottom: 2rem;
}
&.label {
margin-bottom: .5rem;
}
&.actions {
padding-top: 1.5rem;
text-align: center;
+31
View File
@@ -28,6 +28,37 @@ main {
}
}
}
section {
.content {
a {
font-size: inherit;
}
&.text-lg {
p {
font-size: 1.2rem;
margin-bottom: 1em;
line-height: 1.5em;
}
}
&.text-center {
text-align: center;
}
p {
&.mg-bottom-md {
margin-bottom: 2rem;
}
&.actions {
text-align: center;
padding-top: 2rem;
}
}
}
}
}
@media (min-width: 550px) {
+6
View File
@@ -38,6 +38,10 @@ h1, h2, h3, h4, h5, input, button {
font-weight: 300;
}
a {
color: $primary-color;
}
section {
h2 {
font-size: 1.4rem;
@@ -99,10 +103,12 @@ section {
@import "forms";
@import "components/contribution-list";
@import "components/contribution-details";
@import "components/contributor-list";
@import "components/contributor-profile";
@import "components/external-account-link";
@import "components/loading-spinner";
@import "components/proposal-list";
@import "components/topbar";
@import "components/topbar-account-panel";
@import "components/user-avatar";
@@ -0,0 +1,59 @@
section#contribution-details {
header {
nav {
.amount {
font-size: 1.5rem;
}
.symbol {
padding-left: 0.2rem;
}
}
}
.content {
width: 100%;
margin: 0 0 1.5rem;
padding: 2rem 2rem;
border-top: 1px solid rgba(255,255,255,0.2);
border-bottom: 1px solid rgba(255,255,255,0.2);
background-color: rgba(255,255,255,0.1);
h3 {
font-size: 1.5rem;
margin-bottom: 2rem;
}
p {
margin-bottom: 2rem;
span {
font-size: inherit;
}
&.who-what-when {
font-size: 1.2rem;
margin-bottom: 1.2rem;
}
&:last-of-type {
margin-bottom: 0;
}
}
a {
color: $primary-color;
text-decoration: none;
font-size: inherit;
&:hover {
text-decoration: underline;
}
}
}
.actions {
text-align: center;
}
}
@@ -37,6 +37,7 @@ ul.contribution-list {
background-color: rgba(255,255,255,0.1);
font-size: 1.2rem;
border-bottom: 1px solid rgba(255,255,255,0.2);
cursor: pointer;
&:first-of-type {
border-top: 1px solid rgba(255,255,255,0.2);
@@ -52,6 +53,10 @@ ul.contribution-list {
opacity: 0.6;
}
&.selected {
background-color: rgba(255,255,255,0.2);
}
p {
align-self: center;
margin: 0;
@@ -0,0 +1,7 @@
header#topbar section#user-account {
button {
margin-left: 1.2rem;
}
}
+1 -11
View File
@@ -1,16 +1,6 @@
<header id="topbar">
<h1>{{link-to "Kosmos Kredits" "dashboard"}}</h1>
<section id="user-account">
{{#if kredits.hasAccounts }}
{{#if kredits.currentUser}}
{{kredits.currentUser.name}}
{{#if kredits.currentUserIsCore}}(core){{/if}}
{{/if}}
{{else}}
Anonymous
{{/if}}
</section>
{{topbar-account-panel}}
</header>
{{outlet}}
+2
View File
@@ -53,6 +53,7 @@
{{contribution-list contributions=contributionsUnconfirmedSorted
vetoContribution=(action "vetoContribution")
contractInteractionEnabled=kredits.hasAccounts
selectedContributionId=selectedContributionId
showQuickFilter=showQuickFilterUnconfirmed}}
</div>
</section>
@@ -68,6 +69,7 @@
<div class="content">
{{contribution-list contributions=contributionsConfirmedSorted
vetoContribution=(action "vetoContribution")
selectedContributionId=selectedContributionId
showQuickFilter=showQuickFilterConfirmed}}
</div>
</section>
@@ -0,0 +1,47 @@
<section id="contribution-details">
<header class="with-nav">
<h2>Contribution #{{model.id}}</h2>
<nav>
<span class="amount">{{model.amount}}</span>&nbsp;
<span class="symbol">₭S</span>
</nav>
</header>
<div class="content">
<p class="who-what-when">
<span class="contributor">
{{user-avatar contributor=model.contributor}}
{{link-to model.contributor.name "dashboard.contributors.show" model.contributor}}
</span>
contributed
<span class="date" title={{model.iso8601Date}}>{{moment-from model.iso8601Date}}</span>:
</p>
<h3>{{model.description}}</h3>
<p>
Kind: {{model.kind}}
<br>Status: {{contribution-status model}}
</p>
{{#if model.url}}
<p>
<a href={{model.url}}
title={{model.description}}
class="button"
target="_blank"
rel="noopener">
Open URL
</a>
</p>
{{/if}}
</div>
<div class="actions">
<p>
{{#if model.ipfsHash}}
<a href="https://ipfs.io/ipfs/{{model.ipfsHash}}"
class="button small" target="_blank" rel="noopener">
Inspect IPFS data
</a>
{{/if}}
</p>
</div>
</section>
+5
View File
@@ -0,0 +1,5 @@
<main class="center-column">
<section id="signup">
{{outlet}}
</section>
</main>
+15
View File
@@ -0,0 +1,15 @@
<header>
<h2>Welcome aboard!</h2>
</header>
<div class="content text-lg text-center">
<p>
Congratulations. Your initial profile is now complete.
</p>
<p>
Why not say hi to your fellow contributors
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved">in one of our chat rooms</a>?.
</p>
<p class="actions">
{{#link-to "dashboard" class="button small"}}Return to dashboard{{/link-to}}
</p>
</div>
+25
View File
@@ -0,0 +1,25 @@
<header>
<h2>Complete your contributor profile</h2>
</header>
<div class="content text-lg">
<p class="mg-bottom-md">
Kredits allow you to take part in project governance, and to earn rewards for
your contributions. For both, you will need an Ethereum wallet/account.
</p>
<form {{action "submit" on="submit"}}>
<p>
<label>
Ethereum account:<br>
{{input type="text" value=ethAddress
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
class=(if isValidEthAccount "valid" "")}}
</label>
</p>
</form>
<p class="actions">
<button disabled={{signupButtonDisabled}}
{{action "completeSignup"}}>
Complete my profile
</button>
</p>
</div>
+20
View File
@@ -0,0 +1,20 @@
<header>
<h2>Create your contributor profile</h2>
</header>
<div class="content text-lg">
<p>
You have already contributed to a Kosmos project, or you are interested
in contributing in the near future? Fantastic!
</p>
<p>
In order to earn kredits for your contributions, you will need a
registered contributor profile. The quickest way to register is by
connecting one of the following accounts:
</p>
<p class="actions">
<button {{action "connectGithub"}} class="icon">
{{icon-account-github-com}}
Connect GitHub
</button>
</p>
</div>
+9 -3
View File
@@ -12,7 +12,7 @@ module.exports = function(environment) {
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
},
EXTEND_PROTOTYPES: {
// Prevent Ember Data from overriding Date.parse.
@@ -37,6 +37,9 @@ module.exports = function(environment) {
web3ProviderUrl: 'https://rinkeby.infura.io/v3/d4f788b7a6584f7db2fc3c268d4d09e9',
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github',
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github',
ipfs: {
host: 'ipfs.kosmos.org',
port: '5444',
@@ -53,6 +56,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',
@@ -81,8 +87,8 @@ module.exports = function(environment) {
if (process.env.WEB3_PROVIDER_URL) {
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
}
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;
+1518 -5177
View File
File diff suppressed because it is too large Load Diff
+20 -17
View File
@@ -1,6 +1,6 @@
{
"name": "kredits-web",
"version": "1.5.1",
"version": "1.8.0",
"private": true,
"description": "Contribution dashboard of the Kosmos project",
"repository": "https://github.com/67P/kredits-web",
@@ -24,48 +24,51 @@
"deploy": "git push 5apps master"
},
"devDependencies": {
"@ember/jquery": "^0.5.2",
"@ember/optional-features": "^0.6.3",
"@ember/jquery": "^0.6.0",
"@ember/optional-features": "^0.7.0",
"babel-preset-es2015": "^6.22.0",
"babelify": "^7.3.0",
"brfs": "^2.0.2",
"broccoli-asset-rev": "^2.7.0",
"ember-ajax": "^4.0.1",
"broccoli-asset-rev": "^3.0.0",
"ember-ajax": "^5.0.0",
"ember-auto-import": "^1.5.2",
"ember-awesome-macros": "0.41.0",
"ember-cli": "~3.8.1",
"ember-cli": "~3.10.1",
"ember-cli-app-version": "^3.2.0",
"ember-cli-babel": "^7.1.2",
"ember-cli-babel": "^7.7.3",
"ember-cli-chart": "^3.4.0",
"ember-cli-dependency-checker": "^3.1.0",
"ember-cli-eslint": "^4.2.3",
"ember-cli-htmlbars": "^3.0.0",
"ember-cli-htmlbars-inline-precompile": "^1.0.3",
"ember-cli-eslint": "^5.1.0",
"ember-cli-htmlbars": "^3.0.1",
"ember-cli-htmlbars-inline-precompile": "^2.1.0",
"ember-cli-inject-live-reload": "^1.8.2",
"ember-cli-moment-shim": "^3.7.1",
"ember-cli-sass": "^7.2.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-template-lint": "^1.0.0-beta.1",
"ember-cli-uglify": "^2.1.0",
"ember-cli-update": "^0.21.2",
"ember-export-application-global": "^2.0.0",
"ember-flatpickr": "^2.14.0",
"ember-load-initializers": "^1.1.0",
"ember-load-initializers": "^2.0.0",
"ember-macro-helpers": "0.17.0",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-moment": "^7.8.1",
"ember-promise-helpers": "1.0.6",
"ember-qunit": "^3.4.1",
"ember-qunit": "^4.4.1",
"ember-resolver": "^5.0.1",
"ember-source": "~3.8.0",
"ember-source": "~3.10.0",
"ember-truth-helpers": "github:jmurphyau/ember-truth-helpers#31a14373a31f1f82c77537720549b47a95c28e5f",
"eslint-plugin-ember": "^5.2.0",
"eslint-plugin-ember": "^6.2.0",
"eslint-plugin-node": "^9.0.1",
"ethers": "^4.0.27",
"kosmos-schemas": "^2.0.0",
"kredits-contracts": "^5.4.0",
"loader.js": "^4.7.0",
"ndjson": "github:hugomrdias/ndjson#feat/readable-stream3",
"qunit-dom": "^0.8.0",
"qunit-dom": "^0.8.4",
"transform-loader": "^0.2.4",
"tv4": "^1.3.0"
"tv4": "^1.3.0",
"web3-utils": "^1.0.0"
},
"engines": {
"node": "8.* || >= 10.*"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -8,10 +8,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.5.1%2Bc6588097%22%7D%2C%22browserify%22%3A%7B%22tests%22%3Atrue%2C%22transform%22%3A%5B%5B%22babelify%22%2C%7B%22presets%22%3A%5B%22es2015%22%5D%2C%22global%22%3Atrue%7D%5D%5D%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frinkeby.infura.io%2Fv3%2Fd4f788b7a6584f7db2fc3c268d4d09e9%22%2C%22ipfs%22%3A%7B%22host%22%3A%22ipfs.kosmos.org%22%2C%22port%22%3A%225444%22%2C%22protocol%22%3A%22https%22%2C%22gatewayUrl%22%3A%22https%3A%2F%2Fipfs.kosmos.org%2Fipfs%22%7D%2C%22kreditsApmDomain%22%3A%22open.aragonpm.eth%22%2C%22exportApplicationGlobal%22%3Afalse%7D" />
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_JQUERY_INTEGRATION%22%3Atrue%7D%2C%22APP%22%3A%7B%22name%22%3A%22kredits-web%22%2C%22version%22%3A%221.8.0%2B74c8e32c%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%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%22kreditsApmDomain%22%3A%22open.aragonpm.eth%22%2C%22exportApplicationGlobal%22%3Afalse%7D" />
<link integrity="" rel="stylesheet" href="/assets/vendor-179cf27a97ea5bbb0ce72403a401cc8c.css">
<link integrity="" rel="stylesheet" href="/assets/kredits-web-d27bdcdf554d9b3bd6b4d37dd84f4db2.css">
<link integrity="" rel="stylesheet" href="/assets/kredits-web-351b8d4d70a0d451dde0a8f89f828d07.css">
@@ -24,8 +24,8 @@
<body>
<script src="/assets/vendor-13953d1578bf9c8e1d4dec9854bc4804.js" integrity="sha256-F5T7/NqJV3S3xep1UpKy/oQii+9YrqCry6EiZxhkzOA= sha512-y4Kk91N4T4PdPgjLiBRJV7q8XFmQY9Dh5ygJayeRcMRBueWJCe4OPDnrstcJ8C/cjGePUGOFoTmMSbF0wWTB+w==" ></script>
<script src="/assets/kredits-web-686d8b7af100292e31164c098c81d9fe.js" integrity="sha256-HjLTIO3FCh5VYjuLTlJEx4Ek3OX9fcpEpnNTVYjmiXs= sha512-NQqT/Mx6eFztNGI+lmRPYo/cfDSGKHLtttfzh69EV6daCMLtJUW1NfI4xNpErMadPudRMOAG3R5Uis7+qLoJjw==" ></script>
<script src="/assets/vendor-765025e7de61c40a38fb3d7f2c93d4aa.js" integrity="sha256-/Qtv1B7vhqswTyQR74IPIUbG3346Xqb94WqJleIR3v4= sha512-T77HNtXJ47uJ90fMPppaFIoPzyymEX1ZWVkVOkuDWCA/miwGZXeJreaMsE++XphOLB8hvHm26eORNOJECPC37g==" ></script>
<script src="/assets/kredits-web-4a6676cd8de19316b4c92d18e734356d.js" integrity="sha256-JU7jViPyVgzkGYO0+kHJqMCcqO5SoqF/grurGrLLfHQ= sha512-xw4H9fFr0H2F6t+KA2SjIJUu+xz/bBojTIGan1zpukWTOk8Cod2EdIoWuzpVja294CniUwD9AB2tUGTZA/GI1Q==" ></script>
</body>
@@ -0,0 +1,39 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | topbar-account-panel', function(hooks) {
setupRenderingTest(hooks);
test('unknown user without wallet (or no permission to get wallet/account info)', async function(assert) {
await render(hbs`<TopbarAccountPanel />`);
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
});
test('unknown user with Ethereum wallet', async function(assert) {
let service = this.owner.lookup('service:kredits');
service.set('currentUserAccounts', [{ foo: 'bar' }]);
await render(hbs`<TopbarAccountPanel />`);
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
});
test('known contributor', async function(assert) {
let service = this.owner.lookup('service:kredits');
service.set('currentUserAccounts', [{ foo: 'bar' }]);
service.set('currentUser', {
name: 'Dorian Nakamoto',
isCore: false
});
await render(hbs`<TopbarAccountPanel />`);
assert.equal(this.element.textContent.trim(), 'Dorian Nakamoto');
service.set('currentUser.isCore', true);
await render(hbs`<TopbarAccountPanel />`);
assert.equal(this.element.querySelectorAll('span.core-flag').length, 1);
});
});
@@ -11,7 +11,7 @@ module('Integration | Component | user-avatar', function(hooks) {
this.set('bumi', contributors.findBy('id', '1'));
await render(hbs`{{user-avatar contributor=bumi}}`);
assert.equal(this.element.textContent.trim(), '');
assert.dom(this.element).hasText('');
});
test('default image source URL', async function(assert) {
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | dashboard/contributions/show', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:dashboard/contributions/show');
assert.ok(route);
});
});
+11
View File
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | signup', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:signup');
assert.ok(route);
});
});
+11
View File
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | signup/complete', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:signup/complete');
assert.ok(route);
});
});
@@ -0,0 +1,11 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | signup/eth-account', function(hooks) {
setupTest(hooks);
test('it exists', function(assert) {
let route = this.owner.lookup('route:signup/eth-account');
assert.ok(route);
});
});
+29 -29
View File
@@ -5,37 +5,37 @@ import { A } from '@ember/array';
import { module, test } from 'qunit';
import groupBy from 'kredits-web/utils/group-by';
module('Unit | Utils | group-by');
module('Unit | Utils | group-by', function () {
let car1 = { name: 'Carrera', color: 'red' };
let car2 = { name: 'Veyron', color: 'red' };
let car3 = { name: 'Corvette', color: 'blue' };
let car4 = { name: 'Viper', color: 'blue' };
let car5 = { name: 'Cobra', color: 'green' };
let cars = A([car1, car2, car3, car4, car5]);
let car1 = { name: 'Carrera', color: 'red' };
let car2 = { name: 'Veyron', color: 'red' };
let car3 = { name: 'Corvette', color: 'blue' };
let car4 = { name: 'Viper', color: 'blue' };
let car5 = { name: 'Cobra', color: 'green' };
let cars = A([car1, car2, car3, car4, car5]);
test('it groups cars by color', function (assert) {
assert.expect(1);
let redGroup = { property: 'color', value: 'red', items: [car1, car2] };
let blueGroup = { property: 'color', value: 'blue', items: [car3, car4] };
let greenGroup = { property: 'color', value: 'green', items: [car5] };
test('it groups cars by color', function(assert) {
assert.expect(1);
let redGroup = { property: 'color', value: 'red', items: [car1, car2] };
let blueGroup = { property: 'color', value: 'blue', items: [car3, car4] };
let greenGroup = { property: 'color', value: 'green', items: [car5] };
let result = groupBy(cars, 'color');
let expected = [redGroup, blueGroup, greenGroup];
let result = groupBy(cars, 'color');
let expected = [redGroup, blueGroup, greenGroup];
assert.deepEqual(result, expected);
});
assert.deepEqual(result, expected);
});
test('it does not fail with empty array', function(assert) {
let cars = [];
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
});
test('it does not failkwith null', function(assert) {
let cars = null;
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
test('it does not fail with empty array', function (assert) {
let cars = [];
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
});
test('it does not failkwith null', function (assert) {
let cars = null;
let result = groupBy(cars, 'color');
assert.deepEqual(result, []);
});
});