Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1b78cb4cb4
|
|||
| b32f805cbd | |||
|
3110fd347e
|
|||
|
5612ac4e95
|
|||
|
69ab4913aa
|
|||
|
39e01e4558
|
|||
|
7afc75dbff
|
|||
|
f5f74ae27f
|
|||
|
55f7baecf6
|
|||
|
bd199fcf26
|
|||
| 0777c213ba | |||
|
071922d979
|
|||
|
fc044df9fd
|
|||
|
dd2bdd5332
|
|||
|
0cd05d14bc
|
|||
|
3ee140723c
|
|||
|
49b3825af4
|
|||
|
af309ff682
|
|||
|
53f13e4a63
|
|||
|
bcdd033f34
|
|||
|
b9e7737589
|
|||
|
57b9bccb4a
|
|||
|
5c9fbf5043
|
|||
|
205d2d2afc
|
|||
| 895bf569ff | |||
|
dbedf1dbe8
|
|||
| 26f2f2afe9 | |||
|
6c456b1184
|
|||
|
03e7f14d1c
|
|||
|
e94e747ba1
|
|||
|
3679412b3c
|
|||
|
ea15e69d79
|
@@ -31,8 +31,8 @@ You will need the following things properly installed on your computer.
|
|||||||
### Building/running for development
|
### Building/running for development
|
||||||
|
|
||||||
* `npm start` - by default Kredits Web connects to the Rootstock testnet network
|
* `npm start` - by default Kredits Web connects to the Rootstock testnet network
|
||||||
* Visit your app at [http://localhost:4200](http://localhost:4200).
|
* Visit the app at [http://localhost:4200](http://localhost:4200).
|
||||||
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
* Visit the tests at [http://localhost:4200/tests](http://localhost:4200/tests).
|
||||||
|
|
||||||
See [working with locally deployed contracts](https://github.com/67P/kredits-web#working-with-locally-deployed-contracts) for details on how to develop with locally deployed contracts.
|
See [working with locally deployed contracts](https://github.com/67P/kredits-web#working-with-locally-deployed-contracts) for details on how to develop with locally deployed contracts.
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ export default class AddReimbursementComponent extends Component {
|
|||||||
this.exchangeRates.fetchRates();
|
this.exchangeRates.fetchRates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get contributorId () {
|
||||||
|
return this.recipientId || this.kredits.currentUser?.id;
|
||||||
|
}
|
||||||
|
|
||||||
get isValidTotal () {
|
get isValidTotal () {
|
||||||
return isValidAmount(this.total);
|
return isValidAmount(this.total);
|
||||||
}
|
}
|
||||||
@@ -70,7 +74,7 @@ export default class AddReimbursementComponent extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateTotalAmountFromFiat() {
|
updateTotalAmountFromFiat() {
|
||||||
let btcAmount = parseFloat(this.total);
|
let btcAmount = 0;
|
||||||
|
|
||||||
if (this.exchangeRates.btceur > 0 && this.totalEUR > 0) {
|
if (this.exchangeRates.btceur > 0 && this.totalEUR > 0) {
|
||||||
btcAmount += (this.totalEUR / this.exchangeRates.btceur);
|
btcAmount += (this.totalEUR / this.exchangeRates.btceur);
|
||||||
@@ -104,7 +108,7 @@ export default class AddReimbursementComponent extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
updateContributor (event) {
|
updateContributor (event) {
|
||||||
this.recipientId = event.target.value;
|
this.recipientId = parseInt(event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
@@ -136,12 +140,12 @@ export default class AddReimbursementComponent extends Component {
|
|||||||
if (!this.kredits.currentUser) { window.alert('You need to connect your RSK account first.'); return false }
|
if (!this.kredits.currentUser) { window.alert('You need to connect your RSK account first.'); return false }
|
||||||
if (!this.kredits.currentUserIsCore) { window.alert('Only core contributors can submit reimbursements.'); return false }
|
if (!this.kredits.currentUserIsCore) { window.alert('Only core contributors can submit reimbursements.'); return false }
|
||||||
|
|
||||||
const contributor = this.contributors.findBy('id', parseInt(this.recipientId));
|
const contributor = this.contributors.findBy('id', this.contributorId);
|
||||||
|
|
||||||
const attributes = {
|
const attributes = {
|
||||||
amount: parseInt(parseFloat(this.total) * 100000000), // convert to sats
|
amount: parseInt(parseFloat(this.total) * 100000000), // convert to sats
|
||||||
token: config.tokens['BTC'],
|
token: config.tokens['BTC'],
|
||||||
recipientId: parseInt(this.recipientId),
|
recipientId: this.contributorId,
|
||||||
title: `Expenses covered by ${contributor.name}`,
|
title: `Expenses covered by ${contributor.name}`,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
url: this.url,
|
url: this.url,
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
<label>
|
<label>
|
||||||
<p class="label">Contributor:</p>
|
<p class="label">Contributor:</p>
|
||||||
<p>
|
<p>
|
||||||
<select required {{on "change" this.updateContributor}}>
|
<select id="contributor" required {{on "change" this.updateContributor}}>
|
||||||
<option value="" selected disabled hidden></option>
|
|
||||||
{{#each this.contributors as |contributor|}}
|
{{#each this.contributors as |contributor|}}
|
||||||
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
<option value={{contributor.id}} selected={{eq this.contributorId contributor.id}}>{{contributor.name}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
<p class="label">Total amount (BTC):</p>
|
<p class="label">Total amount (BTC):</p>
|
||||||
<p>
|
<p>
|
||||||
<Input @type="text"
|
<Input @type="text"
|
||||||
|
@name="total-btc"
|
||||||
@placeholder="0.0015"
|
@placeholder="0.0015"
|
||||||
@value={{this.total}}
|
@value={{this.total}}
|
||||||
@required={{true}}
|
@required={{true}}
|
||||||
@@ -50,7 +50,9 @@
|
|||||||
|
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
<button {{on "click" this.showExpenseForm}}
|
<button {{on "click" this.showExpenseForm}}
|
||||||
class="green small" type="button">+ Add another item</button>
|
id="add-another-item" class="green small" type="button">
|
||||||
|
+ Add another item
|
||||||
|
</button>
|
||||||
</p>
|
</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p>No line items yet.</p>
|
<p>No line items yet.</p>
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { alias } from '@ember/object/computed';
|
|
||||||
|
|
||||||
export default class BudgetBalancesComponent extends Component {
|
export default class BudgetBalancesComponent extends Component {
|
||||||
@service communityFunds;
|
@service communityFunds;
|
||||||
@alias('communityFunds.balances') balances;
|
|
||||||
|
get balancesSorted () {
|
||||||
|
return this.communityFunds.balances
|
||||||
|
.sortBy('confirmed_balance').reverse();
|
||||||
|
}
|
||||||
|
|
||||||
get loading () {
|
get loading () {
|
||||||
return !this.communityFunds.balancesLoaded;
|
return !this.communityFunds.balancesLoaded;
|
||||||
|
|||||||
@@ -7,11 +7,19 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#each this.balances as |balance|}}
|
{{#each this.balancesSorted as |balance|}}
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{balance.token.symbol}}</th>
|
<th>
|
||||||
<td class="amount">{{balance.confirmed_balance}}</td>
|
<img src={{balance.token.icon}}
|
||||||
<td class="fiat-amount">~{{balance.balanceUSD}} USD</td>
|
alt={{balance.token.description}}
|
||||||
|
title={{balance.token.description}} />
|
||||||
|
</th>
|
||||||
|
<td class="amount">
|
||||||
|
{{fmt-number balance.confirmed_balance}} <span class="unit">sats</span>
|
||||||
|
</td>
|
||||||
|
<td class="fiat-amount">
|
||||||
|
~{{balance.balanceUSD}} USD
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
{{#unless this.isConfirmed}}
|
{{#if this.isConfirmed}}
|
||||||
|
Confirmed at block <strong>{{@confirmedAtBlock}}</strong> (~ {{this.confirmedInHumanTime}} ago)
|
||||||
|
{{else}}
|
||||||
Confirming in <strong>{{this.confirmedInBlocks}}</strong> blocks (~ {{this.confirmedInHumanTime}})
|
Confirming in <strong>{{this.confirmedInBlocks}}</strong> blocks (~ {{this.confirmedInHumanTime}})
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<nav id="main-menu">
|
||||||
|
<ul>
|
||||||
|
<li><LinkTo @route="dashboard">Dashboard</LinkTo></li>
|
||||||
|
<li><LinkTo @route="budget">Budget</LinkTo></li>
|
||||||
|
<li><LinkTo @route="about">About</LinkTo></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import config from 'kredits-web/config/environment';
|
||||||
|
import fmtDateLocalized from 'kredits-web/helpers/fmt-date-localized';
|
||||||
|
|
||||||
|
export default class ReimbursementItemComponent extends Component {
|
||||||
|
@service kredits;
|
||||||
|
@tracked showExpenseDetails = false;
|
||||||
|
|
||||||
|
constructor(owner, args) {
|
||||||
|
super(owner, args);
|
||||||
|
if (this.isUnconfirmed && !this.isVetoed) {
|
||||||
|
this.showExpenseDetails = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get ipfsGatewayUrl () {
|
||||||
|
return config.ipfs.gatewayUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isConfirmed () {
|
||||||
|
return (this.args.reimbursement.confirmedAt - this.kredits.currentBlock) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isUnconfirmed () {
|
||||||
|
return !this.isConfirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isVetoed () {
|
||||||
|
return this.args.reimbursement.vetoed;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showVetoButton () {
|
||||||
|
return this.isUnconfirmed && this.kredits.currentUserIsCore;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showConfirmedIn () {
|
||||||
|
return !this.isVetoed &&
|
||||||
|
(this.showExpenseDetails || this.isUnconfirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
get expenses () {
|
||||||
|
return this.args.reimbursement.expenses;
|
||||||
|
}
|
||||||
|
|
||||||
|
get expensesDateRange () {
|
||||||
|
const dates = this.expenses.map(e => e.date).uniq().sort();
|
||||||
|
let out = fmtDateLocalized.compute(dates.firstObject)
|
||||||
|
if (dates.length > 1) {
|
||||||
|
out += ' - ' + fmtDateLocalized.compute(dates.lastObject)
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggleExpenseDetails () {
|
||||||
|
this.showExpenseDetails = !this.showExpenseDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
veto (id) {
|
||||||
|
this.kredits.vetoReimbursement(id).then(transaction => {
|
||||||
|
console.debug('[controllers:budget] Veto submitted to chain: '+transaction.hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<li data-reimbursement-id={{@reimbursement.id}}
|
||||||
|
class="{{item-status @reimbursement}}"
|
||||||
|
role="button" {{on "click" this.toggleExpenseDetails}}>
|
||||||
|
<p class="meta">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{{#if this.showExpenseDetails}}
|
||||||
|
<ExpenseList @expenses={{@reimbursement.expenses}} />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
<p>
|
||||||
|
{{#if this.showConfirmedIn}}
|
||||||
|
<ConfirmedIn @confirmedAtBlock={{@reimbursement.confirmedAt}} />
|
||||||
|
{{else}}
|
||||||
|
{{#unless this.isVetoed}}{{this.expensesDateRange}}{{/unless}}
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
<p class="actions">
|
||||||
|
<a href="{{this.ipfsGatewayUrl}}/{{@reimbursement.ipfsHash}}"
|
||||||
|
class="button small" target="_blank" rel="noopener noreferrer">
|
||||||
|
Inspect IPFS data
|
||||||
|
</a>
|
||||||
|
{{#if this.showVetoButton}}
|
||||||
|
<button {{on "click" (fn this.veto @reimbursement.id)}}
|
||||||
|
disabled={{@reimbursement.vetoed}}
|
||||||
|
class="button small danger" type="button">veto</button>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
@@ -1,23 +1,10 @@
|
|||||||
import Component from '@glimmer/component';
|
import Component from '@glimmer/component';
|
||||||
import { sort } from '@ember/object/computed';
|
import { sort } from '@ember/object/computed';
|
||||||
import { action } from '@ember/object';
|
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import config from 'kredits-web/config/environment';
|
|
||||||
|
|
||||||
export default class ReimbursementListComponent extends Component {
|
export default class ReimbursementListComponent extends Component {
|
||||||
@service kredits;
|
@service kredits;
|
||||||
|
|
||||||
itemSorting = Object.freeze(['pendingStatus:asc', 'id:desc']);
|
itemSorting = Object.freeze(['pendingStatus:asc', 'id:desc']);
|
||||||
@sort('args.items', 'itemSorting') itemsSorted;
|
@sort('args.items', 'itemSorting') itemsSorted;
|
||||||
|
|
||||||
get ipfsGatewayUrl () {
|
|
||||||
return config.ipfs.gatewayUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
veto (id) {
|
|
||||||
this.kredits.vetoReimbursement(id).then(transaction => {
|
|
||||||
console.debug('[controllers:budget] Veto submitted to chain: '+transaction.hash);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,5 @@
|
|||||||
<ul class="item-list spaced reimbursement-list {{if @loading 'loading'}}">
|
<ul class="item-list collapsible spaced reimbursement-list {{if @loading 'loading'}}">
|
||||||
{{#each this.itemsSorted as |reimbursement|}}
|
{{#each this.itemsSorted as |item|}}
|
||||||
<li data-reimbursement-id={{reimbursement.id}}
|
<ReimbursementItem @reimbursement={{item}} />
|
||||||
class="{{item-status reimbursement}}">
|
|
||||||
<p class="meta">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
{{/if}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default Controller.extend({
|
export default class ApplicationController extends Controller {
|
||||||
kredits: service(),
|
@service kredits;
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { alias } from '@ember/object/computed';
|
import { alias } from '@ember/object/computed';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
export default class BudgetController extends Controller {
|
export default class BudgetController extends Controller {
|
||||||
@service kredits;
|
@service kredits;
|
||||||
|
@service router;
|
||||||
|
|
||||||
@alias('kredits.reimbursementsUnconfirmed') reimbursementsUnconfirmed;
|
@alias('kredits.reimbursementsUnconfirmed') reimbursementsUnconfirmed;
|
||||||
@alias('kredits.reimbursementsConfirmed') reimbursementsConfirmed;
|
@alias('kredits.reimbursementsConfirmed') reimbursementsConfirmed;
|
||||||
|
@alias('kredits.currentUserIsCore') currentUserIsCore;
|
||||||
|
|
||||||
|
@action
|
||||||
|
addReimbursement () {
|
||||||
|
if (!this.kredits.currentUser) {
|
||||||
|
window.alert('You need to connect your RSK account first.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.kredits.currentUserIsCore) {
|
||||||
|
window.alert('Only core contributors can submit reimbursements.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.router.transitionTo('reimbursements.new');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,6 @@ export default Controller.extend({
|
|||||||
|
|
||||||
showFullContributionSync: gt('kredits.missingHistoricContributionsCount', 0),
|
showFullContributionSync: gt('kredits.missingHistoricContributionsCount', 0),
|
||||||
|
|
||||||
showIntroText: computed('kredits.{hasAccounts,currentUser}', function(){
|
|
||||||
return (!this.kredits.hasAccounts || !this.kredits.currentUser);
|
|
||||||
}),
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
||||||
vetoContribution (contributionId) {
|
vetoContribution (contributionId) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { helper } from '@ember/component/helper';
|
import { helper } from '@ember/component/helper';
|
||||||
import getLocale from 'kredits-web/utils/get-locale';
|
import getLocale from 'kredits-web/utils/get-locale';
|
||||||
|
|
||||||
export default helper(function(dateStr) {
|
export default helper(function fmtDateLocalized(dateStr) {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
const locale = getLocale();
|
const locale = getLocale();
|
||||||
return new Intl.DateTimeFormat(locale).format(date);
|
return new Intl.DateTimeFormat(locale).format(date);
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { helper } from '@ember/component/helper';
|
||||||
|
|
||||||
|
export default helper(function fmtNumber(number) {
|
||||||
|
const lang = navigator.language || navigator.userLanguage;
|
||||||
|
return number.toLocaleString(lang);
|
||||||
|
});
|
||||||
+1
-3
@@ -11,7 +11,6 @@ Router.map(function() {
|
|||||||
this.route('contributors', function() {
|
this.route('contributors', function() {
|
||||||
this.route('show', { path: ':id' });
|
this.route('show', { path: ':id' });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('contributions', function() {
|
this.route('contributions', function() {
|
||||||
this.route('show', { path: ':id' });
|
this.route('show', { path: ':id' });
|
||||||
});
|
});
|
||||||
@@ -31,11 +30,10 @@ Router.map(function() {
|
|||||||
});
|
});
|
||||||
this.route('budget', function() {
|
this.route('budget', function() {
|
||||||
this.route('expenses');
|
this.route('expenses');
|
||||||
|
|
||||||
this.route('reimbursements', function() {});
|
this.route('reimbursements', function() {});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('reimbursements', function() {
|
this.route('reimbursements', function() {
|
||||||
this.route('new');
|
this.route('new');
|
||||||
});
|
});
|
||||||
|
this.route('about');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import Route from '@ember/routing/route';
|
||||||
|
|
||||||
|
export default class AboutRoute extends Route {
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import Service, { inject as service } from '@ember/service';
|
|
||||||
import { tracked } from '@glimmer/tracking';
|
|
||||||
import { A } from '@ember/array';
|
import { A } from '@ember/array';
|
||||||
import { task } from 'ember-concurrency-decorators';
|
import { task } from 'ember-concurrency-decorators';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import Service, { inject as service } from '@ember/service';
|
||||||
import config from 'kredits-web/config/environment';
|
import config from 'kredits-web/config/environment';
|
||||||
|
|
||||||
export default class CommunityFundsService extends Service {
|
export default class CommunityFundsService extends Service {
|
||||||
@@ -12,9 +12,20 @@ export default class CommunityFundsService extends Service {
|
|||||||
|
|
||||||
@task
|
@task
|
||||||
*fetchBalances () {
|
*fetchBalances () {
|
||||||
yield fetch(config.btcBalanceAPI).then(res => res.json())
|
const promises = [];
|
||||||
.then(res => {
|
const balances = config.communityFundsAPI.balances;
|
||||||
return this.processBalances(res);
|
|
||||||
|
for (const item of Object.keys(balances)) {
|
||||||
|
const c = balances[item];
|
||||||
|
promises.push(
|
||||||
|
this.fetchBalance(c.url)
|
||||||
|
.then(res => { return this.processBalance(res, c) })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
yield Promise.all(promises)
|
||||||
|
.then(() => {
|
||||||
|
this.balancesLoaded = true;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(`[community-funds] Fetching balances failed:`);
|
console.log(`[community-funds] Fetching balances failed:`);
|
||||||
@@ -22,18 +33,21 @@ export default class CommunityFundsService extends Service {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async processBalances (res) {
|
async fetchBalance(url) {
|
||||||
|
return fetch(url).then(res => res.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async processBalance (res, config) {
|
||||||
await this.exchangeRates.fetchRates();
|
await this.exchangeRates.fetchRates();
|
||||||
|
|
||||||
// Format and round the approximate USD value
|
// Format and round the approximate USD value
|
||||||
const lang = navigator.language || navigator.userLanguage;
|
const lang = navigator.language || navigator.userLanguage;
|
||||||
const balanceUSD = res.confirmed_balance * this.exchangeRates.btcusd;
|
const balanceUSD = (res.confirmed_balance / 100000000) * this.exchangeRates.btcusd;
|
||||||
res.balanceUSD = Math.round(balanceUSD).toLocaleString(lang);
|
res.balanceUSD = Math.round(balanceUSD).toLocaleString(lang);
|
||||||
|
|
||||||
this.balances.pushObject({
|
this.balances.pushObject({
|
||||||
...res,
|
...res,
|
||||||
...{ token: { name: 'BTC', symbol: 'BTC'} }
|
...{ token: { icon: `/img/${config.icon}`, symbol: config.symbol, description: config.description } }
|
||||||
});
|
});
|
||||||
|
|
||||||
this.balancesLoaded = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ export default class ExchangeRatesService extends Service {
|
|||||||
@tracked btceur = 0;
|
@tracked btceur = 0;
|
||||||
@tracked btcusd = 0;
|
@tracked btcusd = 0;
|
||||||
|
|
||||||
|
get exchangeRatesLoaded () {
|
||||||
|
return (this.btceur !== 0) && (this.btcusd !== 0);
|
||||||
|
}
|
||||||
|
|
||||||
async fetchRates (source='bitstamp') {
|
async fetchRates (source='bitstamp') {
|
||||||
|
if (this.exchangeRatesLoaded) return;
|
||||||
|
|
||||||
switch(source) {
|
switch(source) {
|
||||||
case 'bitstamp':
|
case 'bitstamp':
|
||||||
this.btceur = await fetchFromBitstamp('btceur');
|
this.btceur = await fetchFromBitstamp('btceur');
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default Service.extend({
|
|||||||
browserCache: service(),
|
browserCache: service(),
|
||||||
|
|
||||||
currentBlock: null,
|
currentBlock: null,
|
||||||
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
|
currentUserAccounts: null, // default to not having an account. this is the when web3 is loaded.
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
contributors: null,
|
contributors: null,
|
||||||
contributions: null,
|
contributions: null,
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ ul.item-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.collapsible {
|
||||||
|
> li {
|
||||||
|
border-left: 1px solid transparent;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-left: 1px solid $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.loading {
|
&.loading {
|
||||||
@include loading-border-top;
|
@include loading-border-top;
|
||||||
|
|
||||||
|
|||||||
+21
-35
@@ -1,3 +1,7 @@
|
|||||||
|
body {
|
||||||
|
margin-bottom: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 1rem 2rem;
|
padding: 1rem 2rem;
|
||||||
|
|
||||||
@@ -14,7 +18,7 @@ main {
|
|||||||
"contributions";
|
"contributions";
|
||||||
}
|
}
|
||||||
|
|
||||||
&#budget {
|
&#budget, &#about {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-row-gap: 2rem;
|
grid-row-gap: 2rem;
|
||||||
@@ -56,13 +60,12 @@ main {
|
|||||||
|
|
||||||
&.text-lg {
|
&.text-lg {
|
||||||
p {
|
p {
|
||||||
font-size: 1.2rem;
|
font-size: 1.35rem;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 2rem;
|
||||||
line-height: 1.5em;
|
line-height: 150%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove after switch to Tailwind CSS
|
|
||||||
&.text-center {
|
&.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -74,40 +77,23 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#intro {
|
&.text {
|
||||||
padding: 2rem;
|
h2 {
|
||||||
background-color: rgba(0,0,0,.2);
|
margin: 4rem 0 2rem 0;
|
||||||
font-size: 1.6rem;
|
|
||||||
|
|
||||||
@include media-max(small) {
|
&:first-of-type {
|
||||||
padding: 2rem 1rem;
|
margin-top: 0;
|
||||||
font-size: 1.5rem;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
p {
|
||||||
font-size: inherit;
|
font-size: 1.35rem;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 2rem;
|
||||||
}
|
line-height: 150%;
|
||||||
|
}
|
||||||
p {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
line-height: 1.5em;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover, &:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +114,7 @@ main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&#budget {
|
&#budget, &#about {
|
||||||
grid-column-gap: 3rem;
|
grid-column-gap: 3rem;
|
||||||
grid-template-columns: 2fr 4fr 2fr;
|
grid-template-columns: 2fr 4fr 2fr;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ section {
|
|||||||
@import "components/contributor-profile";
|
@import "components/contributor-profile";
|
||||||
@import "components/expense-list";
|
@import "components/expense-list";
|
||||||
@import "components/external-account-link";
|
@import "components/external-account-link";
|
||||||
|
@import "components/main-navigation-menu";
|
||||||
@import "components/loading-spinner";
|
@import "components/loading-spinner";
|
||||||
@import "components/reimbursement-list";
|
@import "components/reimbursement-list";
|
||||||
@import "components/topbar";
|
@import "components/topbar";
|
||||||
|
|||||||
@@ -16,8 +16,12 @@ section#funds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
font-size: 1.2rem;
|
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 1.5rem;
|
||||||
|
max-width: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
@@ -35,8 +39,13 @@ section#funds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.fiat-amount {
|
&.fiat-amount {
|
||||||
|
font-size: 1.2rem;
|
||||||
color: rgba(255,255,255,0.8);
|
color: rgba(255,255,255,0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.unit {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
nav#main-menu {
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: rgba(0,0,0,.2);
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 3rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
|
||||||
|
@include media-max(small) {
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&.active, &:hover {
|
||||||
|
color: $yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,9 +43,6 @@ ul.reimbursement-list {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 1.6rem 0 1rem 0;
|
padding: 1.6rem 0 1rem 0;
|
||||||
|
|
||||||
&.confirmation-eta {
|
|
||||||
}
|
|
||||||
|
|
||||||
&.actions {
|
&.actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
h1 {
|
h1 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<main id="about">
|
||||||
|
<div id="aside">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
<section class="text">
|
||||||
|
<h2>What is this?</h2>
|
||||||
|
<p>
|
||||||
|
You have found the contribution dashboard and budget management app of the
|
||||||
|
<a href="https://kosmos.org" target="_blank" rel="noreferrer noopener">Kosmos</a>
|
||||||
|
open-source co-operative.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We use this app to track what people contribute to all parts of our
|
||||||
|
projects (not just code), as well as for managing our community's budget.
|
||||||
|
</p>
|
||||||
|
<h2>Why?</h2>
|
||||||
|
<p>
|
||||||
|
We are trying out a new form of co-operative, native to the Internet,
|
||||||
|
creating and sharing digital resources the same way traditional
|
||||||
|
co-operatives share physical ones.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By knowing roughly how much people contribute, we are able to reward
|
||||||
|
co-operative members who contribute their time with money contributed
|
||||||
|
by the ones who don't.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In addition to open-source grant payouts, all contributors can also propose
|
||||||
|
reimbursements for specific expenses they covered on behalf of the
|
||||||
|
community.
|
||||||
|
</p>
|
||||||
|
<h2>How?</h2>
|
||||||
|
<p>
|
||||||
|
Instead of notaries, lawyers, courts, or banks, we record data and manage
|
||||||
|
certain decisions on a decentralized ledger called
|
||||||
|
<a href="https://rootstock.io/" target="_blank" rel="noreferrer noopener">Rootstock</a>,
|
||||||
|
which is cryptographically tied to the bitcoin timechain.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By doing this, we can ensure community control over the budget, as well as
|
||||||
|
full transparency and verifiability of everything that happens. It also
|
||||||
|
allows us to quickly, cheaply, and reliably send our open-source grants to
|
||||||
|
anyone who's contributing, no matter where they are on this beautiful
|
||||||
|
planet.
|
||||||
|
</p>
|
||||||
|
<h2>Kredits</h2>
|
||||||
|
<p>
|
||||||
|
All contributions are rewarded with so-called kredits. They are both
|
||||||
|
credits in the traditional sense of public attribution, as well as a
|
||||||
|
measure of how much and how regularly someone has added value to the
|
||||||
|
community and its products and services.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Kredits are considered for example for grant payouts, as well as
|
||||||
|
permissions for certain actions like vetos or votes. They may also be used
|
||||||
|
to access <a href="https://kosmos.org/services/">hosted services</a> for free,
|
||||||
|
or to unlock additional features on otherwise free services.
|
||||||
|
</p>
|
||||||
|
<h2>Getting started</h2>
|
||||||
|
<p>
|
||||||
|
We'd be delighted to welcome you as a new contributor!
|
||||||
|
If you'd like to start collecting kredits for your contributions, you can
|
||||||
|
<LinkTo @route="signup">create a contributor profile</LinkTo> now.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Next, you could learn more about
|
||||||
|
<a href="https://community.kosmos.org/t/how-kredits-for-contributions-are-proposed-and-confirmed/176">how kredits are proposed and issued</a>.
|
||||||
|
If you want to dive deeper into how this all works, head over to the
|
||||||
|
<a href="https://wiki.kosmos.org/Kredits">Kredits documentation</a>
|
||||||
|
page on our (soon to be phased out) wiki.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="empty">
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<header id="topbar">
|
<header id="topbar">
|
||||||
<h1><LinkTo @route="dashboard">Kosmos Kredits</LinkTo></h1>
|
<h1><LinkTo @route="dashboard">Kredits</LinkTo></h1>
|
||||||
<TopbarAccountPanel />
|
<TopbarAccountPanel />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<MainNavigationMenu />
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
|||||||
+11
-19
@@ -1,22 +1,6 @@
|
|||||||
<main id="budget">
|
<main id="budget">
|
||||||
|
|
||||||
<div id="aside">
|
<div id="aside">
|
||||||
<!-- <section id="main-nav"> -->
|
|
||||||
<!-- <header> -->
|
|
||||||
<!-- <h2>Budget</h2> -->
|
|
||||||
<!-- </header> -->
|
|
||||||
<!-- <div class="content"> -->
|
|
||||||
<!-- <ul> -->
|
|
||||||
<!-- <li> -->
|
|
||||||
<!-- <LinkTo @route="budget.expenses">Expenses</LinkTo> -->
|
|
||||||
<!-- </li> -->
|
|
||||||
<!-- <li> -->
|
|
||||||
<!-- <a href="#">Rewards</a> -->
|
|
||||||
<!-- </li> -->
|
|
||||||
<!-- </ul> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- </section> -->
|
|
||||||
|
|
||||||
<section id="funds">
|
<section id="funds">
|
||||||
<header>
|
<header>
|
||||||
<h2>Community funds</h2>
|
<h2>Community funds</h2>
|
||||||
@@ -33,7 +17,11 @@
|
|||||||
<header class="with-nav">
|
<header class="with-nav">
|
||||||
<h2>Proposed Reimbursements</h2>
|
<h2>Proposed Reimbursements</h2>
|
||||||
<nav>
|
<nav>
|
||||||
<LinkTo @route="reimbursements.new" @title="Submit a reimbursement" class="button small green">add</LinkTo>
|
<button type="button" class="button small green"
|
||||||
|
title="Submit a reimbursement"
|
||||||
|
{{on "click" this.addReimbursement}}>
|
||||||
|
add
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -45,9 +33,13 @@
|
|||||||
{{#if this.reimbursementsConfirmed}}
|
{{#if this.reimbursementsConfirmed}}
|
||||||
<section id="expenses-confirmed">
|
<section id="expenses-confirmed">
|
||||||
<header class="with-nav">
|
<header class="with-nav">
|
||||||
<h2>Confirmed Expenses</h2>
|
<h2>Confirmed Reimbursements</h2>
|
||||||
<nav>
|
<nav>
|
||||||
<LinkTo @route="reimbursements.new" @title="Submit a reimbursement" class="button small green">add</LinkTo>
|
<button type="button" class="button small green"
|
||||||
|
title="Submit a reimbursement"
|
||||||
|
{{on "click" this.addReimbursement}}>
|
||||||
|
add
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
@@ -1,19 +1,3 @@
|
|||||||
{{#if this.showIntroText}}
|
|
||||||
<div id="intro" class={{if this.showDetailsPane "with-details"}}>
|
|
||||||
<h2>
|
|
||||||
Welcome to the contribution dashboard of the
|
|
||||||
<a href="https://kosmos.org" target="_blank" rel="noreferrer noopener">Kosmos</a> project!
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
If you want to learn more about what the numbers mean and how this works,
|
|
||||||
check out the
|
|
||||||
<a href="https://wiki.kosmos.org/Kredits" target="_blank" rel="noreferrer noopener">Kredits documentation</a>.
|
|
||||||
If you want to start earning kredits for your contributions,
|
|
||||||
<LinkTo @route="signup">create a contributor profile</LinkTo>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
<main id="dashboard" class={{if this.showDetailsPane "with-details"}}>
|
<main id="dashboard" class={{if this.showDetailsPane "with-details"}}>
|
||||||
|
|
||||||
<div id="stats">
|
<div id="stats">
|
||||||
|
|||||||
+20
-1
@@ -45,7 +45,22 @@ module.exports = function(environment) {
|
|||||||
'BTC': '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'
|
'BTC': '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'
|
||||||
},
|
},
|
||||||
|
|
||||||
btcBalanceAPI: 'https://api.kosmos.org/kredits/onchain_btc_balance',
|
communityFundsAPI: {
|
||||||
|
balances: {
|
||||||
|
onchain: {
|
||||||
|
icon: 'icon-btc.png',
|
||||||
|
symbol: 'BTC',
|
||||||
|
description: 'BTC on chain',
|
||||||
|
url: 'https://api.kosmos.org/btcpay/onchain_btc_balance'
|
||||||
|
},
|
||||||
|
lightning: {
|
||||||
|
icon: 'icon-btc-lightning.png',
|
||||||
|
symbol: 'BTC',
|
||||||
|
description: 'BTC on Lightning Network',
|
||||||
|
url: 'https://api.kosmos.org/btcpay/lightning_btc_balance'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
corsProxy: 'https://cors.5apps.com/?uri='
|
corsProxy: 'https://cors.5apps.com/?uri='
|
||||||
};
|
};
|
||||||
@@ -66,6 +81,10 @@ module.exports = function(environment) {
|
|||||||
protocol: 'http',
|
protocol: 'http',
|
||||||
gatewayUrl: 'http://localhost:8080/ipfs'
|
gatewayUrl: 'http://localhost:8080/ipfs'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Uncomment if you want to use local akkounts
|
||||||
|
// ENV.communityFundsAPI.balances.onchain.url = 'http://localhost:3000/api/btcpay/onchain_btc_balance';
|
||||||
|
// ENV.communityFundsAPI.balances.lightning.url = 'http://localhost:3000/api/btcpay/lightning_btc_balance';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (environment === 'test') {
|
if (environment === 'test') {
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ module.exports = function(defaults) {
|
|||||||
path: "empty" // needed for kosmos-schemas dependency
|
path: "empty" // needed for kosmos-schemas dependency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
fingerprint: {
|
||||||
|
exclude: [ 'img/icon-btc' ]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Generated
+13
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@babel/eslint-parser": "^7.19.1",
|
||||||
@@ -6816,7 +6816,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001332",
|
"version": "1.0.30001599",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
|
||||||
|
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -6826,9 +6828,12 @@
|
|||||||
{
|
{
|
||||||
"type": "tidelift",
|
"type": "tidelift",
|
||||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"license": "CC-BY-4.0"
|
|
||||||
},
|
},
|
||||||
"node_modules/capture-exit": {
|
"node_modules/capture-exit": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@@ -29050,7 +29055,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001332",
|
"version": "1.0.30001599",
|
||||||
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz",
|
||||||
|
"integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"capture-exit": {
|
"capture-exit": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Contribution dashboard of the Kosmos project",
|
"description": "Contribution dashboard of the Kosmos project",
|
||||||
"repository": "https://github.com/67P/kredits-web",
|
"repository": "https://github.com/67P/kredits-web",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+250
-182
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
+3
-3
@@ -8,10 +8,10 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
|
|
||||||
|
|
||||||
<meta name="kredits-web/config/environment" content="%7B%22modulePrefix%22%3A%22kredits-web%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22auto%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%7D%2C%22_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" />
|
<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.2.1%2Bb32f805c%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%22communityFundsAPI%22%3A%7B%22balances%22%3A%7B%22onchain%22%3A%7B%22icon%22%3A%22icon-btc.png%22%2C%22symbol%22%3A%22BTC%22%2C%22description%22%3A%22BTC%20on%20chain%22%2C%22url%22%3A%22https%3A%2F%2Fapi.kosmos.org%2Fbtcpay%2Fonchain_btc_balance%22%7D%2C%22lightning%22%3A%7B%22icon%22%3A%22icon-btc-lightning.png%22%2C%22symbol%22%3A%22BTC%22%2C%22description%22%3A%22BTC%20on%20Lightning%20Network%22%2C%22url%22%3A%22https%3A%2F%2Fapi.kosmos.org%2Fbtcpay%2Flightning_btc_balance%22%7D%7D%7D%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/vendor-ca96b2e19fc9f356e3dbad90c9f3f323.css">
|
||||||
<link integrity="" rel="stylesheet" href="/assets/kredits-web-f21602587acec9a1744c244385a83592.css">
|
<link integrity="" rel="stylesheet" href="/assets/kredits-web-1ce689f6aada92a7d79a1947300931cf.css">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<script src="/assets/vendor-4d87b3e0995c5bb18e46836a089900a4.js" integrity="sha256-5WmWY05Kd+n98Sw0GwuaWodmDVBZ2yVGGtC/2owVfQE= sha512-tiGPA5zvfhnSC9LPTmpvXJmOu30yhsdPqSiZJMPGmE/ez/xJLMaM29vjs4Cs74cDf6fX4uYSGRIu1KfOyGpJYA==" ></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>
|
<script src="/assets/kredits-web-71e8e218f4178df50b78058520257c0d.js" integrity="sha256-pQsThEeIoXS7h22Jg3XPg+5l268k7S63rhGhhHC10o8= sha512-CV9qAyQL3jdXW4+DmTdaGFtGYZ9Lz+f4+8aUnQyK+wOeDkLpUultyhhF2P1+wHS6Hm2vj7n213Ef89YblxDgUA==" ></script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,13 +1,58 @@
|
|||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render } from '@ember/test-helpers';
|
import { click, fillIn, render } from '@ember/test-helpers';
|
||||||
import { hbs } from 'ember-cli-htmlbars';
|
import { hbs } from 'ember-cli-htmlbars';
|
||||||
|
import contributors from '../../../fixtures/contributors';
|
||||||
|
|
||||||
module('Integration | Component | add-reimbursement', function(hooks) {
|
module('Integration | Component | add-reimbursement', function(hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
test('it works', async function(assert) {
|
hooks.beforeEach(function() {
|
||||||
|
let kredits = this.owner.lookup('service:kredits');
|
||||||
|
kredits.set('contributors', contributors);
|
||||||
|
kredits.set('currentUser', contributors.findBy('id', 3));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Contributor select menu', async function(assert) {
|
||||||
await render(hbs`<AddReimbursement />`);
|
await render(hbs`<AddReimbursement />`);
|
||||||
assert.ok(true);
|
|
||||||
|
assert.equal(this.element.querySelectorAll('select#contributor option').length, contributors.length,
|
||||||
|
'contains correct amount of items');
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('select#contributor option:checked').value, "3",
|
||||||
|
'preselects the connected contributor account');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Adding expense items', async function(assert) {
|
||||||
|
await render(hbs`<AddReimbursement />`);
|
||||||
|
|
||||||
|
assert.dom(this.element).includesText('New expense item');
|
||||||
|
|
||||||
|
await fillIn('form input[name="expense-amount"]', '49');
|
||||||
|
await fillIn('form input[name="expense-title"]', 'Domain kosmos.org (yearly fee)');
|
||||||
|
await click('form#add-expense-item input[type="submit"]');
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-eur"]').value, '49',
|
||||||
|
'updates the total EUR amount');
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-usd"]').value, '0',
|
||||||
|
'does not update the total USD amount');
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-btc"]').value, '0.00534493',
|
||||||
|
'updates the total BTC amount');
|
||||||
|
|
||||||
|
assert.dom(this.element).doesNotIncludeText('New expense item');
|
||||||
|
|
||||||
|
await click('button#add-another-item');
|
||||||
|
|
||||||
|
await fillIn('form input[name="expense-amount"]', '29');
|
||||||
|
await fillIn('select[name="expense-currency"]', 'USD');
|
||||||
|
await fillIn('form input[name="expense-title"]', 'Domain kosmos.social (yearly fee)');
|
||||||
|
await click('form#add-expense-item input[type="submit"]');
|
||||||
|
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-usd"]').value, '29',
|
||||||
|
'updates the total USD amount');
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-eur"]').value, '49',
|
||||||
|
'does not update the total EUR amount');
|
||||||
|
assert.equal(this.element.querySelector('input[name="total-btc"]').value, '0.00804268',
|
||||||
|
'updates the total BTC amount');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user