Compare commits
39 Commits
v2.0.0-beta.2
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
8cc3da40b7
|
|||
|
bdeab512b6
|
|||
| 97ebebe28c | |||
|
d5edb93ec5
|
|||
|
0ecc4079c2
|
|||
|
3f080cf553
|
|||
|
0bdb4d2021
|
|||
|
1e7d8491f9
|
|||
|
4356288497
|
|||
|
87bf8ff2df
|
|||
|
26975636a0
|
|||
| dad56a7b31 | |||
|
d069fa02ab
|
|||
|
d219abc358
|
|||
|
13989bf973
|
|||
|
b3ab69d599
|
|||
|
fe3816501c
|
|||
| 854402aa73 | |||
|
abc19c715b
|
|||
|
b6434ac8d2
|
|||
|
4788b8c51c
|
|||
|
6a72a5b418
|
|||
|
d90ddd464a
|
|||
|
2626543f3e
|
|||
| 088db24534 | |||
| f7b7daa024 | |||
| bb11e2267b | |||
| 7cd023a21b | |||
|
1095bf0218
|
|||
|
138cec0389
|
|||
|
a93be41e08
|
|||
|
89f6fa0b5c
|
|||
|
e2a80eafd1
|
|||
|
662e76979b
|
|||
|
6c7de97e38
|
|||
|
fe27b010da
|
|||
|
ff716c68ea
|
|||
|
84dded2f10
|
|||
|
f86190030f
|
@@ -0,0 +1,30 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
@@ -48,6 +48,12 @@ module.exports = {
|
||||
"no-action"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/expense-list/template",
|
||||
"only": [
|
||||
"no-invalid-role"
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "app/components/topbar-account-panel/template",
|
||||
"only": [
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "14"
|
||||
- "16"
|
||||
|
||||
sudo: false
|
||||
dist: precise
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
addons:
|
||||
chrome: stable
|
||||
|
||||
cache:
|
||||
npm: false
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
@@ -1,9 +1,15 @@
|
||||
[](https://travis-ci.org/67P/kredits-web)
|
||||
[](https://github.com/67P/kredits-web/actions/workflows/ci.yml?query=branch%3Amaster)
|
||||
|
||||
# kredits-web
|
||||
# Kredits Web
|
||||
|
||||
This README outlines the details of collaborating on this Ember application.
|
||||
A short introduction of this app could easily go here.
|
||||
This is the main Web UI for Kosmos [Kredits](https://wiki.kosmos.org/Kredits).
|
||||
It provides an overview of contributors and contributions, as well as the
|
||||
community's budget, expenses, and reimbursements.
|
||||
|
||||
It is an unhosted Web app, fetching all data from
|
||||
[Rootstock](https://rootstock.io/) and [IPFS](https://ipfs.tech/), and caching
|
||||
it in the browser's local database. As such, you can run the app directly from
|
||||
the `/release` directory on any Web server that can serve static assets.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -24,7 +30,7 @@ You will need the following things properly installed on your computer.
|
||||
|
||||
### Building/running for development
|
||||
|
||||
* `ember serve` - by default kredits-web connects to the Ethreum Kovan network
|
||||
* `npm start` - by default Kredits Web connects to the Rootstock testnet network
|
||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
||||
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
||||
|
||||
@@ -48,7 +54,7 @@ Make use of the many generators for code, try `ember help generate` for more det
|
||||
### Building
|
||||
|
||||
* `ember build` (development)
|
||||
* `ember build --environment production` (production)
|
||||
* `npm run build` (production)
|
||||
|
||||
### Deploying
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<form onsubmit={{action "submit"}}>
|
||||
<p>
|
||||
<label for="c-account">Ethereum account</label>
|
||||
<label for="c-account">Rootstock account</label>
|
||||
<Input @type="text"
|
||||
@value={{this.account}}
|
||||
name="account" id="c-account"
|
||||
|
||||
@@ -44,26 +44,10 @@
|
||||
|
||||
<h3>Expense items</h3>
|
||||
{{#if this.expenses}}
|
||||
<ul class="expense-list">
|
||||
{{#each this.expenses as |expense|}}
|
||||
<li>
|
||||
<div class="description" rowspan="2">
|
||||
<h4>
|
||||
<span class="date">{{expense.date}}:</span>
|
||||
<span class="title">{{expense.title}}</span>
|
||||
</h4>
|
||||
<p class="description">{{expense.description}}</p>
|
||||
</div>
|
||||
<div class="amount">
|
||||
{{fmt-fiat-currency expense.amount expense.currency}}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button {{on "click" (fn this.removeExpenseItem expense)}}
|
||||
class="danger small" type="button">delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<ExpenseList @expenses={{this.expenses}}
|
||||
@removeExpenseItem={{this.removeExpenseItem}}
|
||||
@deletable={{true}} />
|
||||
|
||||
<p class="actions">
|
||||
<button {{on "click" this.showExpenseForm}}
|
||||
class="green small" type="button">+ Add another item</button>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import moment from 'moment';
|
||||
|
||||
export default class ConfirmedInComponent extends Component {
|
||||
@service kredits;
|
||||
|
||||
get confirmedInBlocks () {
|
||||
return this.args.confirmedAtBlock - this.kredits.currentBlock;
|
||||
}
|
||||
|
||||
get confirmedInSeconds () {
|
||||
// A new block is mined every 30 seconds on average
|
||||
return this.confirmedInBlocks * 30;
|
||||
}
|
||||
|
||||
get confirmedInHumanTime () {
|
||||
return moment.duration(this.confirmedInSeconds, "seconds").humanize();
|
||||
}
|
||||
|
||||
get isConfirmed () {
|
||||
return this.confirmedInBlocks <= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{#unless this.isConfirmed}}
|
||||
Confirming in <strong>{{this.confirmedInBlocks}}</strong> blocks (~ {{this.confirmedInHumanTime}})
|
||||
{{/unless}}
|
||||
@@ -0,0 +1,9 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
export default class ExpenseListComponent extends Component {
|
||||
|
||||
get showDeleteButton () {
|
||||
return !!this.args.deletable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<ul class="expense-list">
|
||||
{{#each @expenses as |expense|}}
|
||||
<li class="expense-item">
|
||||
<h4>
|
||||
<span class="date">{{fmt-date-localized expense.date}}:</span>
|
||||
<span class="title">{{expense.title}}</span>
|
||||
</h4>
|
||||
<div class="amount">
|
||||
{{fmt-fiat-currency expense.amount expense.currency}}
|
||||
</div>
|
||||
<p class="description">
|
||||
{{expense.description}}
|
||||
</p>
|
||||
<p class="tags">
|
||||
{{#each expense.tags as |tag|}}
|
||||
<button type="button" class="small yellow" role="none">
|
||||
<IconTag />{{tag}}
|
||||
</button>
|
||||
{{/each}}
|
||||
</p>
|
||||
{{#if this.showDeleteButton}}
|
||||
<div class="actions">
|
||||
<button {{on "click" (fn @removeExpenseItem expense)}}
|
||||
class="danger small" type="button">delete</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
@@ -3,44 +3,36 @@
|
||||
<li data-reimbursement-id={{reimbursement.id}}
|
||||
class="{{item-status reimbursement}}">
|
||||
<p class="meta">
|
||||
<span class="title">
|
||||
Expenses covered by {{reimbursement.contributor.name}}
|
||||
</span>
|
||||
<span class="recipient">
|
||||
<UserAvatar @contributor={{reimbursement.contributor}} />
|
||||
</span>
|
||||
<span class="title">
|
||||
Expenses covered by {{reimbursement.contributor.name}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="token-amount">
|
||||
<span class="amount">
|
||||
{{sats-to-btc reimbursement.amount}}</span> <span class="symbol">BTC</span>
|
||||
</p>
|
||||
<ul class="expense-list">
|
||||
{{#each reimbursement.expenses as |expense|}}
|
||||
<li>
|
||||
<div class="description" rowspan="2">
|
||||
<h4>
|
||||
<span class="date">{{expense.date}}:</span>
|
||||
<span class="title">{{expense.title}}</span>
|
||||
</h4>
|
||||
<p class="description">{{expense.description}}</p>
|
||||
</div>
|
||||
<div class="amount">
|
||||
{{fmt-fiat-currency expense.amount expense.currency}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{#if this.kredits.currentUserIsCore}}
|
||||
<p>
|
||||
|
||||
<ExpenseList @expenses={{reimbursement.expenses}} />
|
||||
|
||||
<div class="meta">
|
||||
<p class="confirmation-eta">
|
||||
<ConfirmedIn @confirmedAtBlock={{reimbursement.confirmedAt}} />
|
||||
</p>
|
||||
<p class="actions">
|
||||
<a href="{{this.ipfsGatewayUrl}}/{{reimbursement.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener noreferrer">
|
||||
Inspect IPFS data
|
||||
</a>
|
||||
{{#if this.kredits.currentUserIsCore}}
|
||||
<button {{on "click" (fn this.veto reimbursement.id)}}
|
||||
disabled={{reimbursement.vetoed}}
|
||||
class="button small danger" type="button">veto</button>
|
||||
</p>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
@@ -1,45 +1,37 @@
|
||||
import Component from '@ember/component';
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { computed } from '@ember/object';
|
||||
import { action } from '@ember/object';
|
||||
import { isPresent } from '@ember/utils';
|
||||
|
||||
export default Component.extend({
|
||||
export default class TopbarAccountPanelComponent extends Component {
|
||||
@service router;
|
||||
@service kredits;
|
||||
|
||||
tagName: '',
|
||||
@tracked setupInProgress = false;
|
||||
|
||||
kredits: service(),
|
||||
router: service(),
|
||||
|
||||
setupInProgress: false,
|
||||
|
||||
userHasEthereumWallet: computed(function() {
|
||||
get userHasWallet () {
|
||||
return isPresent(window.ethereum);
|
||||
}),
|
||||
}
|
||||
|
||||
showConnectButton: computed('userHasEthereumWallet',
|
||||
'kredits.hasAccounts', function() {
|
||||
return this.userHasEthereumWallet &&
|
||||
!this.kredits.hasAccounts;
|
||||
}),
|
||||
get walletConnected () {
|
||||
return this.userHasWallet && this.kredits.hasAccounts;
|
||||
}
|
||||
|
||||
actions: {
|
||||
get walletDisconnected () {
|
||||
return this.userHasWallet && !this.kredits.hasAccounts;
|
||||
}
|
||||
|
||||
@action
|
||||
signup () {
|
||||
this.router.transitionTo('signup');
|
||||
},
|
||||
|
||||
async connectAccount() {
|
||||
try {
|
||||
await window.ethereum.enable();
|
||||
this.set('setupInProgress', true);
|
||||
await this.kredits.setup();
|
||||
this.set('setupInProgress', false);
|
||||
} catch (error) {
|
||||
this.set('setupInProgress', false);
|
||||
console.log('Opening Ethereum wallet failed:', error);
|
||||
}
|
||||
|
||||
@action
|
||||
async connectWallet () {
|
||||
this.setupInProgress = true;
|
||||
await this.kredits.connectWallet();
|
||||
this.setupInProgress = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
{{#if this.setupInProgress}}
|
||||
Connecting account...
|
||||
{{else}}
|
||||
{{#if (and this.kredits.hasAccounts this.kredits.currentUser)}}
|
||||
{{#if (and this.walletConnected 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>
|
||||
<button onclick={{action "signup"}} id="signup"
|
||||
class="small" type="button">Sign up</button>
|
||||
{{#if this.walletDisconnected}}
|
||||
<button onclick={{action "connectWallet"}} id="connect"
|
||||
class="small green" type="button">Connect wallet</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, not, sort } from '@ember/object/computed';
|
||||
import { alias, gt, not, sort } from '@ember/object/computed';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Controller.extend({
|
||||
@@ -33,6 +33,8 @@ export default Controller.extend({
|
||||
showQuickFilterUnconfirmed: false,
|
||||
showQuickFilterConfirmed: false,
|
||||
|
||||
showFullContributionSync: gt('kredits.missingHistoricContributionsCount', 0),
|
||||
|
||||
showIntroText: computed('kredits.{hasAccounts,currentUser}', function(){
|
||||
return (!this.kredits.hasAccounts || !this.kredits.currentUser);
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import config from 'kredits-web/config/environment';
|
||||
import { isAddress } from 'web3-utils';
|
||||
|
||||
export default class AccountController extends Controller {
|
||||
@service kredits;
|
||||
|
||||
@tracked accountAddress = null;
|
||||
|
||||
get isValidEthAccount () {
|
||||
return isAddress(this.accountAddress);
|
||||
}
|
||||
|
||||
get signupButtonDisabled () {
|
||||
return !this.isValidEthAccount;
|
||||
}
|
||||
|
||||
@action
|
||||
completeSignup () {
|
||||
const payload = {
|
||||
accessToken: this.kredits.githubAccessToken,
|
||||
account: this.accountAddress
|
||||
}
|
||||
|
||||
fetch(config.githubSignupUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert('Creating profile failed. We have been notified about this error and will take a look soon. Sorry!');
|
||||
console.warn('Creating contributor profile failed:',
|
||||
JSON.parse(data.error.body).error.message)
|
||||
return false;
|
||||
} else {
|
||||
console.log('[signup/account] Created contributor:', data);
|
||||
|
||||
this.kredits.githubAccessToken = null;
|
||||
this.accountAddress = null;
|
||||
|
||||
this.transitionToRoute('signup.complete');
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
@@ -1,14 +1,12 @@
|
||||
import Controller from '@ember/controller';
|
||||
import { action } from '@ember/object';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default Controller.extend({
|
||||
|
||||
actions: {
|
||||
export default class IndexController extends Controller {
|
||||
|
||||
@action
|
||||
connectGithub () {
|
||||
window.location = config.githubConnectUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
import getLocale from 'kredits-web/utils/get-locale';
|
||||
|
||||
export default helper(function(dateStr) {
|
||||
const date = new Date(dateStr);
|
||||
const locale = getLocale();
|
||||
return new Intl.DateTimeFormat(locale).format(date);
|
||||
});
|
||||
+1
-1
@@ -26,7 +26,7 @@ Router.map(function() {
|
||||
});
|
||||
this.route('signup', function() {
|
||||
this.route('github');
|
||||
this.route('eth-account');
|
||||
this.route('account');
|
||||
this.route('complete');
|
||||
});
|
||||
this.route('budget', function() {
|
||||
|
||||
@@ -11,8 +11,11 @@ export default class DashboardRoute extends Route {
|
||||
schedule('afterRender', this.kredits.syncContributions,
|
||||
this.kredits.syncContributions.perform);
|
||||
}
|
||||
schedule('afterRender', this.kredits.fetchMissingContributions,
|
||||
this.kredits.fetchMissingContributions.perform);
|
||||
// TODO fetch automatically under a certain threshold
|
||||
// The browser might delete cached data and we don't need manual re-syncs
|
||||
// depending on how little is missing
|
||||
// schedule('afterRender', this.kredits.fetchMissingContributions,
|
||||
// this.kredits.fetchMissingContributions.perform);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ export default Route.extend({
|
||||
contributors: alias('kredits.contributors'),
|
||||
|
||||
model (params) {
|
||||
return this.contributors.findBy('id', params.id);
|
||||
const contributor = this.contributors.findBy('id', params.id);
|
||||
|
||||
if (contributor) {
|
||||
return contributor;
|
||||
} else {
|
||||
return this.kredits.fetchContributor(params.id);
|
||||
}
|
||||
},
|
||||
|
||||
setupController (controller, model) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
|
||||
export default class SignupAccountRoute extends Route {
|
||||
@service kredits;
|
||||
|
||||
async setupController (controller) {
|
||||
if (!window.ethereum) return;
|
||||
|
||||
if (this.kredits.hasAccounts) {
|
||||
controller.accountAddress = this.kredits.currentUserAccounts.firstObject;
|
||||
} else {
|
||||
return this.kredits.connectWallet();
|
||||
}
|
||||
}
|
||||
|
||||
redirect () {
|
||||
if (isEmpty(this.kredits.githubAccessToken)) {
|
||||
this.transitionTo('signup.index');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -22,7 +22,7 @@ export default Route.extend({
|
||||
|
||||
this.kredits.set('githubAccessToken', accessToken);
|
||||
|
||||
this.transitionTo('signup.eth-account');
|
||||
this.transitionTo('signup.account');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@ import config from 'kredits-web/config/environment';
|
||||
|
||||
function createStore(name) {
|
||||
let networkName;
|
||||
if (config.web3RequiredNetworkName) {
|
||||
networkName = config.web3RequiredNetworkName.toLocaleLowerCase().replace(' ', '-');
|
||||
if (config.web3NetworkName) {
|
||||
networkName = config.web3NetworkName.toLocaleLowerCase().replace(' ', '-');
|
||||
} else {
|
||||
networkName = 'custom';
|
||||
}
|
||||
|
||||
+70
-28
@@ -5,7 +5,7 @@ import Service from '@ember/service';
|
||||
import EmberObject from '@ember/object';
|
||||
import { computed } from '@ember/object';
|
||||
import { alias, filterBy, notEmpty, sort } from '@ember/object/computed';
|
||||
import { isEmpty, isPresent } from '@ember/utils';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
import { task, taskGroup } from 'ember-concurrency';
|
||||
@@ -15,6 +15,7 @@ import processContributorData from 'kredits-web/utils/process-contributor-data';
|
||||
import processContributionData from 'kredits-web/utils/process-contribution-data';
|
||||
import processReimbursementData from 'kredits-web/utils/process-reimbursement-data';
|
||||
import formatKredits from 'kredits-web/utils/format-kredits';
|
||||
import switchNetwork from 'kredits-web/utils/switch-network';
|
||||
|
||||
import config from 'kredits-web/config/environment';
|
||||
import Contributor from 'kredits-web/models/contributor';
|
||||
@@ -48,11 +49,28 @@ export default Service.extend({
|
||||
contributionsNeedSync: false,
|
||||
reimbursementsNeedSync: false,
|
||||
|
||||
missingHistoricContributionsCount: 0,
|
||||
|
||||
init () {
|
||||
this._super(...arguments);
|
||||
this.set('contributors', []);
|
||||
this.set('contributions', []);
|
||||
this.set('reimbursements', []);
|
||||
|
||||
if (window.ethereum) {
|
||||
window.ethereum.on('chainChanged', this.handleUserChainChanged);
|
||||
window.ethereum.on('accountsChanged', this.handleAccountsChanged);
|
||||
}
|
||||
},
|
||||
|
||||
handleUserChainChanged (chainId) {
|
||||
console.log('User-provided chain ID changed to', chainId);
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
handleAccountsChanged (accounts) {
|
||||
console.log('User-provided accounts changed to', accounts);
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
// This is called in the application route's beforeModel(). So it is
|
||||
@@ -62,7 +80,7 @@ export default Service.extend({
|
||||
let ethProvider;
|
||||
|
||||
return new Promise(resolve => {
|
||||
function instantiateWithoutAccount () {
|
||||
function instantiateWithoutWallet () {
|
||||
console.debug('[kredits] Creating new instance from npm module class');
|
||||
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
|
||||
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
|
||||
@@ -72,44 +90,50 @@ export default Service.extend({
|
||||
});
|
||||
}
|
||||
|
||||
async function instantiateWithAccount (web3Provider, context) {
|
||||
async function instantiateWithWallet (web3Provider, context) {
|
||||
console.debug('[kredits] Using user-provided Web3 instance, e.g. from Metamask');
|
||||
ethProvider = new ethers.providers.Web3Provider(web3Provider);
|
||||
|
||||
const network = await ethProvider.getNetwork();
|
||||
if (isPresent(config.web3RequiredChainId) &&
|
||||
network.chainId !== config.web3RequiredChainId) {
|
||||
return instantiateWithoutAccount();
|
||||
}
|
||||
const accounts = await ethProvider.listAccounts();
|
||||
const chainId = config.web3ChainId;
|
||||
|
||||
ethProvider.listAccounts().then(accounts => {
|
||||
if (isEmpty(accounts)) return instantiateWithoutWallet();
|
||||
|
||||
if (network.chainId !== chainId) {
|
||||
return switchNetwork();
|
||||
} else {
|
||||
context.set('currentUserAccounts', accounts);
|
||||
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
|
||||
resolve({
|
||||
ethProvider,
|
||||
ethSigner
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (window.ethereum) {
|
||||
if (window.ethereum.isConnected()) {
|
||||
instantiateWithAccount(window.ethereum, this);
|
||||
instantiateWithWallet(window.ethereum, this);
|
||||
} else {
|
||||
instantiateWithoutAccount();
|
||||
}
|
||||
}
|
||||
// Legacy dapp browsers...
|
||||
else if (window.web3) {
|
||||
instantiateWithAccount(window.web3.currentProvider, this);
|
||||
}
|
||||
// Non-dapp browsers...
|
||||
else {
|
||||
instantiateWithoutAccount();
|
||||
instantiateWithoutWallet();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async connectWallet () {
|
||||
const provider = new ethers.providers.Web3Provider(window.ethereum);
|
||||
const network = await provider.getNetwork();
|
||||
const chainId = config.web3ChainId;
|
||||
const chainIdHex = `0x${Number(chainId).toString(16)}`;
|
||||
|
||||
try {
|
||||
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
||||
if (network.chainId !== chainId) await switchNetwork(chainIdHex);
|
||||
} catch (err) {
|
||||
console.log('Connecting wallet failed:', err);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async setup () {
|
||||
const kredits = await this.getEthProvider().then(providerAndSigner => {
|
||||
return new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
|
||||
@@ -118,9 +142,12 @@ export default Service.extend({
|
||||
});
|
||||
|
||||
await kredits.init();
|
||||
|
||||
this.set('kredits', kredits);
|
||||
this.set('currentBlock', await kredits.provider.getBlockNumber());
|
||||
this.set('currentBlock', await this.kredits.provider.getBlockNumber());
|
||||
this.kredits.provider.on('block', blockNumber => {
|
||||
console.debug('[kredits] New block mined:', blockNumber);
|
||||
this.set('currentBlock', blockNumber)
|
||||
});
|
||||
|
||||
if (this.currentUserAccounts && this.currentUserAccounts.length > 0) {
|
||||
this.getCurrentUser.then(contributorData => {
|
||||
@@ -227,12 +254,20 @@ export default Service.extend({
|
||||
await this.loadObjectsFromCache('Contribution');
|
||||
this.set('contributionsNeedSync', true);
|
||||
} else {
|
||||
await this.fetchContributions({ page: { size: 30 } });
|
||||
await this.fetchContributions({ page: { size: 40 } });
|
||||
}
|
||||
|
||||
await this.updateMissingHistoricContributionsCount();
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
async updateMissingHistoricContributionsCount () {
|
||||
const contributionsCount = await this.kredits.Contribution.count;
|
||||
this.set('missingHistoricContributionsCount', contributionsCount - this.contributions.length);
|
||||
console.debug(`Missing ${this.missingHistoricContributionsCount} historic contributions (out of ${contributionsCount} overall)`)
|
||||
},
|
||||
|
||||
addContributor (attributes) {
|
||||
if (attributes.github_uid) {
|
||||
const uidInt = parseInt(attributes.github_uid);
|
||||
@@ -261,6 +296,12 @@ export default Service.extend({
|
||||
});
|
||||
},
|
||||
|
||||
async fetchContributor (id) {
|
||||
console.debug(`[kredits] Fetching contributor from the network`);
|
||||
return this.kredits.Contributor.getById(id)
|
||||
.then(data => this.loadContributorFromData(data))
|
||||
},
|
||||
|
||||
fetchContributors () {
|
||||
console.debug(`[kredits] Fetching all contributors from the network`);
|
||||
return this.kredits.Contributor.all()
|
||||
@@ -368,11 +409,12 @@ export default Service.extend({
|
||||
syncContributions: task(function * () {
|
||||
yield this.fetchNewContributions.perform();
|
||||
yield this.syncUnconfirmedContributions.perform();
|
||||
yield this.updateMissingHistoricContributionsCount();
|
||||
this.set('contributionsNeedSync', false);
|
||||
}).group('contributionTasks'),
|
||||
|
||||
fetchNewContributions: task(function * () {
|
||||
const count = yield this.kredits.Contribution.functions.contributionsCount();
|
||||
const count = yield this.kredits.Contribution.count;
|
||||
const lastKnownContributionId = Math.max.apply(null, this.contributions.mapBy('id'));
|
||||
const toFetch = count - lastKnownContributionId;
|
||||
|
||||
@@ -389,7 +431,7 @@ export default Service.extend({
|
||||
}),
|
||||
|
||||
fetchMissingContributions: task(function * () {
|
||||
const count = yield this.kredits.Contribution.functions.contributionsCount();
|
||||
const count = yield this.kredits.Contribution.count;
|
||||
const allIds = [...Array(count+1).keys()];
|
||||
allIds.shift(); // remove first item, which is 0
|
||||
const loadedContributions = new Set(this.contributions.mapBy('id'));
|
||||
@@ -738,5 +780,5 @@ export default Service.extend({
|
||||
this.contributors
|
||||
.findBy('address', to)
|
||||
.incrementProperty('balance', value);
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
+32
-11
@@ -31,32 +31,53 @@ button, input[type=submit], .button {
|
||||
&.small {
|
||||
font-size: 0.86rem;
|
||||
padding: 0.2rem 0.8rem;
|
||||
|
||||
svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.danger:not(:disabled) {
|
||||
color: $red;
|
||||
background-color: rgba(40, 21, 21, 0.6);
|
||||
border-color: rgba(40, 21, 21, 1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(40, 21, 21, 0.8);
|
||||
}
|
||||
&:focus, &:active, &.active {
|
||||
border-color: $red;
|
||||
}
|
||||
&:hover { background-color: rgba(40, 21, 21, 0.8); }
|
||||
&:focus, &:active, &.active { border-color: $red; }
|
||||
}
|
||||
|
||||
&.green:not(:disabled) {
|
||||
color: $green;
|
||||
background-color: rgba(21, 40, 21, 0.6);
|
||||
border-color: rgba(21, 40, 21, 1);
|
||||
&:hover { background-color: rgba(21, 40, 21, 0.8); }
|
||||
&:focus, &:active, &.active { border-color: $green; }
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(21, 40, 21, 0.8);
|
||||
&.pink:not(:disabled) {
|
||||
color: $pink;
|
||||
background-color: rgba(40, 21, 40, 0.6);
|
||||
border-color: rgba(40, 21, 40, 1);
|
||||
&:hover { background-color: rgba(40, 21, 40, 0.8); }
|
||||
&:focus, &:active, &.active { border-color: $pink; }
|
||||
}
|
||||
&:focus, &:active, &.active {
|
||||
border-color: $green;
|
||||
|
||||
&.purple:not(:disabled) {
|
||||
color: $purple;
|
||||
background-color: rgba(24, 21, 40, 0.6);
|
||||
border-color: rgba(24, 21, 40, 1);
|
||||
&:hover { background-color: rgba(24, 21, 40, 0.8); }
|
||||
&:focus, &:active, &.active { border-color: $purple; }
|
||||
}
|
||||
|
||||
&.yellow:not(:disabled) {
|
||||
color: $yellow;
|
||||
background-color: rgba(40, 40, 21, 0.6);
|
||||
border-color: rgba(40, 40, 21, 1);
|
||||
&:hover { background-color: rgba(40, 40, 21, 0.8); }
|
||||
&:focus, &:active, &.active { border-color: $yellow; }
|
||||
}
|
||||
|
||||
&.icon {
|
||||
|
||||
@@ -14,10 +14,6 @@ section#signup {
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.label {
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: 1rem;
|
||||
|
||||
@@ -62,15 +62,12 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove after switch to Tailwind CSS
|
||||
&.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
&.mg-bottom-md {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
@@ -177,4 +174,9 @@ main section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Remove after switch to Tailwind CSS
|
||||
.mb-4 { margin-bottom: 1rem; }
|
||||
.mb-8 { margin-bottom: 2rem; }
|
||||
}
|
||||
|
||||
|
||||
@@ -17,32 +17,30 @@ ul.expense-list {
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
grid-row-start: 1;
|
||||
grid-row-end: 3;
|
||||
}
|
||||
|
||||
.amount {
|
||||
justify-self: end;
|
||||
// font-weight: normal;
|
||||
}
|
||||
|
||||
.actions {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: normal;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 2rem;
|
||||
.amount {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
&.description {
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
opacity: 0.7;
|
||||
grid-column-start: span 2;
|
||||
}
|
||||
|
||||
.tags {
|
||||
button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,4 +31,24 @@ ul.reimbursement-list {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.meta {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
margin-top: 0.6rem;
|
||||
border-top: 1px solid $item-border-color;
|
||||
display: flex;
|
||||
|
||||
p {
|
||||
flex: 1;
|
||||
padding: 1.6rem 0 1rem 0;
|
||||
|
||||
&.confirmation-eta {
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tag"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
|
||||
|
After Width: | Height: | Size: 355 B |
@@ -45,6 +45,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{#if this.contributionsConfirmed}}
|
||||
<section id="contributions-by-type">
|
||||
<header>
|
||||
<h2>Contributions by type</h2>
|
||||
@@ -53,6 +54,7 @@
|
||||
<ChartContributionsByType @contributions={{this.contributions}} />
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div id="contributions">
|
||||
@@ -87,6 +89,7 @@
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.contributionsConfirmed}}
|
||||
<section id="contributions-confirmed">
|
||||
<header class="with-nav">
|
||||
<h2>Confirmed Contributions</h2>
|
||||
@@ -105,6 +108,33 @@
|
||||
@showQuickFilter={{this.showQuickFilterConfirmed}} />
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showFullContributionSync}}
|
||||
<section id="sync-all-contributions">
|
||||
{{#if this.kredits.fetchMissingContributions.isIdle}}
|
||||
<p class="mb-4">
|
||||
There are
|
||||
<strong>{{this.kredits.missingHistoricContributionsCount}}</strong>
|
||||
earlier contributions, which are not currently loaded/displayed.
|
||||
</p>
|
||||
<p>
|
||||
You can fetch all historic data in one go, and have it stored locally in
|
||||
your browser:
|
||||
<button type="button" {{on "click" (perform this.kredits.fetchMissingContributions)}} class="small">
|
||||
fetch all data
|
||||
</button>
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="mb-4">
|
||||
Syncing data. Please be patient...
|
||||
</p>
|
||||
<p>
|
||||
(You can leave this website anytime and sync missing data when you come back.)
|
||||
</p>
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div id="details">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<div class="actions">
|
||||
<p>
|
||||
<a href="https://rinkeby.etherscan.io/address/{{this.model.account}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect Ethereum transactions</a>
|
||||
<a href="https://explorer.testnet.rsk.co/address/{{this.model.account}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect Rootstock 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}}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<header>
|
||||
<h2>Complete your contributor profile</h2>
|
||||
</header>
|
||||
<div class="content text-lg">
|
||||
<p class="mb-8">
|
||||
Kredits allow you to to earn rewards for your contributions, in the form of
|
||||
dynamic open-source grants. As a regular contributor, you can also take
|
||||
part in the community's project governance and finances.
|
||||
</p>
|
||||
<p>
|
||||
In order to interact with the system you will need a
|
||||
<a href="https://rootstock.io" target="_blank" rel="noopener noreferrer">Rootstock</a>
|
||||
wallet/account.
|
||||
</p>
|
||||
<form>
|
||||
<p>
|
||||
<label>
|
||||
Rootstock address:<br>
|
||||
<Input @type="text" @value={{this.accountAddress}} placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4" class={{if this.isValidEthAccount "valid" ""}} />
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
<p class="actions">
|
||||
<button {{on "click" (fn this.completeSignup)}}
|
||||
disabled={{this.signupButtonDisabled}}
|
||||
type="button">
|
||||
Complete my profile
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -7,7 +7,8 @@
|
||||
</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>?.
|
||||
<a href="https://wiki.kosmos.org/Main_Page#Community_.2F_Getting_in_touch_.2F_Getting_involved"
|
||||
target="_blank" rel="noopener noreferrer">in one of our chat rooms</a>?.
|
||||
</p>
|
||||
<p class="actions">
|
||||
<LinkTo @route="dashboard" class="button small">Return to dashboard</LinkTo>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
<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,5 @@
|
||||
export default function() {
|
||||
return (navigator.languages && navigator.languages.length) ?
|
||||
navigator.languages[0] :
|
||||
navigator.language;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default async function () {
|
||||
const networkName = config.web3NetworkName;
|
||||
const chainId = config.web3ChainId;
|
||||
const chainIdHex = `0x${Number(chainId).toString(16)}`;
|
||||
|
||||
try {
|
||||
await window.ethereum.request({
|
||||
method: 'wallet_switchEthereumChain',
|
||||
params: [{ chainId: chainIdHex }]
|
||||
});
|
||||
} catch (err) {
|
||||
// This error code indicates that the chain has not been added to MetaMask
|
||||
if (err.code === 4902) {
|
||||
await window.ethereum.request({
|
||||
method: 'wallet_addEthereumChain',
|
||||
params: [{
|
||||
chainId: chainIdHex,
|
||||
chainName: networkName,
|
||||
rpcUrls: [ config.web3ProviderUrl ],
|
||||
nativeCurrency: { name: 'tRBTC', symbol: 'tRBTC', decimals: 18 }
|
||||
}]
|
||||
});
|
||||
} else {
|
||||
console.warn('Failed to switch chains:', err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -26,11 +26,11 @@ module.exports = function(environment) {
|
||||
},
|
||||
|
||||
web3ProviderUrl: 'https://rsk-testnet.kosmos.org',
|
||||
web3RequiredChainId: 31,
|
||||
web3RequiredNetworkName: 'RSK Testnet',
|
||||
web3ChainId: 31,
|
||||
web3NetworkName: 'RSK Testnet',
|
||||
|
||||
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github',
|
||||
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github',
|
||||
githubConnectUrl: 'https://hal8000.kosmos.chat/kredits/signup/connect/github',
|
||||
githubSignupUrl: 'https://hal8000.kosmos.chat/kredits/signup/github',
|
||||
|
||||
ipfs: {
|
||||
host: 'ipfs.kosmos.org',
|
||||
@@ -82,7 +82,12 @@ module.exports = function(environment) {
|
||||
|
||||
if (process.env.WEB3_PROVIDER_URL) {
|
||||
ENV.web3ProviderUrl = process.env.WEB3_PROVIDER_URL;
|
||||
ENV.web3RequiredChainId = null;
|
||||
}
|
||||
if (process.env.WEB3_CHAIN_ID) {
|
||||
ENV.web3ChainId = parseInt(process.env.WEB3_CHAIN_ID);
|
||||
}
|
||||
if (process.env.WEB3_NETWORK_NAME) {
|
||||
ENV.web3NetworkName = process.env.WEB3_NETWORK_NAME;
|
||||
}
|
||||
|
||||
return ENV;
|
||||
|
||||
Generated
+9
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"version": "2.0.0-beta.2",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kredits-web",
|
||||
"version": "2.0.0-beta.2",
|
||||
"version": "2.0.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
@@ -14,7 +14,7 @@
|
||||
"@ember/optional-features": "^1.3.0",
|
||||
"@glimmer/component": "^1.0.0",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
"@kredits/contracts": "^7.0.1",
|
||||
"@kredits/contracts": "^7.3.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"ember-auto-import": "^1.12.1",
|
||||
@@ -3067,9 +3067,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@kredits/contracts": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.0.1.tgz",
|
||||
"integrity": "sha512-4IhNQoPc1BCT63NKyDOmV+ryb82FKNbIqfDyTdant6WTI9lfkIAnI8Et+ynQmqxx3Efez5hqoDZtEhCijcuIng==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.3.0.tgz",
|
||||
"integrity": "sha512-7Bo2LFEqGIK39G54AStas1RWouBOp+r7Vcv2vP1uLbY01Usea8SHvd2OjLP6nFgsQa0n/rto9oremHv5IAylrQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@kosmos/schemas": "^3.1.0",
|
||||
@@ -26066,9 +26066,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@kredits/contracts": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.0.1.tgz",
|
||||
"integrity": "sha512-4IhNQoPc1BCT63NKyDOmV+ryb82FKNbIqfDyTdant6WTI9lfkIAnI8Et+ynQmqxx3Efez5hqoDZtEhCijcuIng==",
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.3.0.tgz",
|
||||
"integrity": "sha512-7Bo2LFEqGIK39G54AStas1RWouBOp+r7Vcv2vP1uLbY01Usea8SHvd2OjLP6nFgsQa0n/rto9oremHv5IAylrQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@kosmos/schemas": "^3.1.0",
|
||||
|
||||
+3
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kredits-web",
|
||||
"version": "2.0.0-beta.2",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"description": "Contribution dashboard of the Kosmos project",
|
||||
"repository": "https://github.com/67P/kredits-web",
|
||||
@@ -17,12 +17,11 @@
|
||||
"start": "ember serve",
|
||||
"test": "npm-run-all lint:* test:*",
|
||||
"test:ember": "ember test",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:8545 ember serve",
|
||||
"start:local": "WEB3_PROVIDER_URL=http://localhost:8545 WEB3_CHAIN_ID=1337 WEB3_NETWORK_NAME='Hardhat Devchain' 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/",
|
||||
"postversion": "git push origin master",
|
||||
"deploy": "git push 5apps master"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -31,7 +30,7 @@
|
||||
"@ember/optional-features": "^1.3.0",
|
||||
"@glimmer/component": "^1.0.0",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
"@kredits/contracts": "^7.0.1",
|
||||
"@kredits/contracts": "^7.3.0",
|
||||
"babel-preset-es2015": "^6.22.0",
|
||||
"broccoli-asset-rev": "^3.0.0",
|
||||
"ember-auto-import": "^1.12.1",
|
||||
|
||||
File diff suppressed because one or more lines are too long
+363
-213
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
+2
-2
File diff suppressed because one or more lines are too long
+4
-4
@@ -8,10 +8,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
|
||||
|
||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%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%222.0.0-beta.2%2B62b618b6%22%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frsk-testnet.kosmos.org%22%2C%22web3RequiredChainId%22%3A31%2C%22web3RequiredNetworkName%22%3A%22RSK%20Testnet%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%22tokens%22%3A%7B%22BTC%22%3A%220x2260fac5e5542a773aa44fbcfedf7c193bc2c599%22%7D%2C%22btcBalanceAPI%22%3A%22https%3A%2F%2Fapi.kosmos.org%2Fkredits%2Fonchain_btc_balance%22%2C%22corsProxy%22%3A%22https%3A%2F%2Fcors.5apps.com%2F%3Furi%3D%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%222.0.0%2Bbdeab512%22%7D%2C%22web3ProviderUrl%22%3A%22https%3A%2F%2Frsk-testnet.kosmos.org%22%2C%22web3ChainId%22%3A31%2C%22web3NetworkName%22%3A%22RSK%20Testnet%22%2C%22githubConnectUrl%22%3A%22https%3A%2F%2Fhal8000.kosmos.chat%2Fkredits%2Fsignup%2Fconnect%2Fgithub%22%2C%22githubSignupUrl%22%3A%22https%3A%2F%2Fhal8000.kosmos.chat%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%22tokens%22%3A%7B%22BTC%22%3A%220x2260fac5e5542a773aa44fbcfedf7c193bc2c599%22%7D%2C%22btcBalanceAPI%22%3A%22https%3A%2F%2Fapi.kosmos.org%2Fkredits%2Fonchain_btc_balance%22%2C%22corsProxy%22%3A%22https%3A%2F%2Fcors.5apps.com%2F%3Furi%3D%22%2C%22exportApplicationGlobal%22%3Afalse%7D" />
|
||||
|
||||
<link integrity="" rel="stylesheet" href="/assets/vendor-ca96b2e19fc9f356e3dbad90c9f3f323.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-429511daa3257050f23812f202a534ee.css">
|
||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-f21602587acec9a1744c244385a83592.css">
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
<body>
|
||||
|
||||
|
||||
<script src="/assets/vendor-4ee536ded971436a83d35253df25f2cb.js" integrity="sha256-yb+pC9iuZ8WcQwk9VuDNhSlWguL7BEABsEOEhBpwUC0= sha512-50LApx5XqeKHu/FMaljzqhAMPgkqSiy2rDhUcViYBcXWBBYmilFirup/iWvLiNCdfr5ct9wk7+tU9iShdSgDww==" ></script>
|
||||
<script src="/assets/kredits-web-0a433c90d05ea3903308867e0d178f7f.js" integrity="sha256-kU8qbqT24o1tZj4xdYJzarfPFSfEmZr3JLu0g8pAmqk= sha512-gD6YmV3xDnwHJ2WU6yXNkUEiPScjdfM2S5eYwCrsfcsI4iJUG81z7IjUbwzstuZocKkCS5zt8Y84Hh84SdhSuw==" ></script>
|
||||
<script src="/assets/vendor-4d87b3e0995c5bb18e46836a089900a4.js" integrity="sha256-5WmWY05Kd+n98Sw0GwuaWodmDVBZ2yVGGtC/2owVfQE= sha512-tiGPA5zvfhnSC9LPTmpvXJmOu30yhsdPqSiZJMPGmE/ez/xJLMaM29vjs4Cs74cDf6fX4uYSGRIu1KfOyGpJYA==" ></script>
|
||||
<script src="/assets/kredits-web-ae44ce0c1a892c2da424f8002b037b86.js" integrity="sha256-ZT6R9QwhnMEgs1X3pmlX6Oh5N4zJgXWLF8al8PTOKqk= sha512-BYrlNe9cti4ZEcp//b5FhPGvmAE08vTEC3WT15EpTqTXccNkOqFJuiGob+ymxIDfDzVU6vN8szxCTBOUe61Tmw==" ></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
@@ -3,37 +3,90 @@ import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
|
||||
function stubWalletApi () {
|
||||
window.ethereum = {
|
||||
on: function() { return true; },
|
||||
request: function() { return Promise.resolve(); }
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
test('Unknown user without wallet', async function(assert) {
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
|
||||
assert.ok(
|
||||
this.element.textContent.trim().match(/^Anonymous/),
|
||||
'shows current user as anonymous'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#signup').length, 1,
|
||||
'signup button is visible'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#connect').length, 0,
|
||||
'connect button is not visible'
|
||||
);
|
||||
});
|
||||
|
||||
test('unknown user with Ethereum wallet', async function(assert) {
|
||||
test('Unknown user with disconnected wallet', async function(assert) {
|
||||
stubWalletApi();
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('currentUserAccounts', []);
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.ok(
|
||||
this.element.textContent.trim().match(/^Anonymous/),
|
||||
'shows current user as anonymous'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#signup').length, 1,
|
||||
'signup button is visible'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#connect').length, 1,
|
||||
'connect button is visible'
|
||||
);
|
||||
});
|
||||
|
||||
test('Unknown user with connected wallet', async function(assert) {
|
||||
stubWalletApi();
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('currentUserAccounts', [{ foo: 'bar' }]);
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.ok(this.element.textContent.trim().match(/^Anonymous/));
|
||||
assert.ok(
|
||||
this.element.textContent.trim().match(/^Anonymous/),
|
||||
'shows current user as anonymous'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#signup').length, 1,
|
||||
'signup button is visible'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#connect').length, 0,
|
||||
'connect button is not visible'
|
||||
);
|
||||
});
|
||||
|
||||
test('known contributor', async function(assert) {
|
||||
test('Known contributor', async function(assert) {
|
||||
stubWalletApi();
|
||||
let service = this.owner.lookup('service:kredits');
|
||||
service.set('currentUserAccounts', [{ foo: 'bar' }]);
|
||||
service.set('currentUser', {
|
||||
name: 'Dorian Nakamoto',
|
||||
isCore: false
|
||||
});
|
||||
service.set('currentUser', { name: 'Dorian Nakamoto', isCore: false });
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.dom(this.element).hasText('Dorian Nakamoto');
|
||||
|
||||
service.set('currentUser.isCore', true);
|
||||
await render(hbs`<TopbarAccountPanel />`);
|
||||
|
||||
assert.equal(this.element.querySelectorAll('span.core-flag').length, 1);
|
||||
assert.dom(this.element).hasText(
|
||||
'Dorian Nakamoto', 'shows current user\'s name'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#signup').length, 0,
|
||||
'signup button is not visible'
|
||||
);
|
||||
assert.equal(
|
||||
this.element.querySelectorAll('button#connect').length, 0,
|
||||
'connect button is not visible'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import createComponent from 'kredits-web/tests/helpers/create-component';
|
||||
// import moment from 'moment';
|
||||
|
||||
module('Unit | Component | confirmed-in', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let component = createComponent('component:confirmed-in');
|
||||
assert.ok(component);
|
||||
});
|
||||
|
||||
test('#confirmedInBlocks', function(assert) {
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
kredits.set('currentBlock', 419550);
|
||||
let component = createComponent('component:confirmed-in');
|
||||
component.args.confirmedAtBlock = 420000;
|
||||
|
||||
assert.equal(component.confirmedInBlocks, 450);
|
||||
})
|
||||
|
||||
test('#confirmedInSeconds', function(assert) {
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
kredits.set('currentBlock', 419550);
|
||||
let component = createComponent('component:confirmed-in');
|
||||
component.args.confirmedAtBlock = 420000;
|
||||
|
||||
assert.equal(component.confirmedInSeconds, 13500);
|
||||
})
|
||||
|
||||
test('#confirmedInHumanTime', function(assert) {
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
kredits.set('currentBlock', 419550);
|
||||
let component = createComponent('component:confirmed-in');
|
||||
component.args.confirmedAtBlock = 420000;
|
||||
|
||||
assert.equal(component.confirmedInHumanTime, '4 hours');
|
||||
})
|
||||
|
||||
test('#isConfirmed', function(assert) {
|
||||
const kredits = this.owner.lookup('service:kredits');
|
||||
kredits.set('currentBlock', 430000);
|
||||
let component = createComponent('component:confirmed-in');
|
||||
component.args.confirmedAtBlock = 420000;
|
||||
|
||||
assert.ok(component.isConfirmed);
|
||||
})
|
||||
});
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | signup/eth-account', function(hooks) {
|
||||
module('Unit | Route | signup/account', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:signup/eth-account');
|
||||
let route = this.owner.lookup('route:signup/account');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user