Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
}
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
{{#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>
|
<LinkTo @route="reimbursements.new" @title="Submit a reimbursement" class="button small green">add</LinkTo>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -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
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/eslint-parser": "^7.19.1",
|
"@babel/eslint-parser": "^7.19.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-web",
|
"name": "kredits-web",
|
||||||
"version": "2.0.0",
|
"version": "2.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Contribution dashboard of the Kosmos project",
|
"description": "Contribution dashboard of the Kosmos project",
|
||||||
"repository": "https://github.com/67P/kredits-web",
|
"repository": "https://github.com/67P/kredits-web",
|
||||||
|
|||||||
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
File diff suppressed because one or more lines are too long
+229
-162
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.0%2B0777c213%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-dab5f8c2df81891a0eb6d4d2bbf24a27.js" integrity="sha256-5XKNFa8JPKQWJztf69+Qhuszl/RV5uIYlg8/aPYp0ZY= sha512-0NYDa2a5cJBY6LxPDrEtaqD9YpuDEH7oR17jLvoMzWHBx4WIwerNRc9uadXwg+7UMnua3ysxt8M6i9uaJKRhTA==" ></script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user