Compare commits

...

21 Commits

Author SHA1 Message Date
Râu Cao 8cc3da40b7 2.0.0 2023-08-14 18:28:02 +02:00
Râu Cao bdeab512b6 Upgrade kredits-contracts 2023-08-14 17:57:04 +02:00
Râu Cao 97ebebe28c Merge pull request #214 from 67P/chore/remove_last_ethereum_bits
Remove mentions of Ethereum from UI and code, refactor wallet connect
2023-08-14 17:53:44 +02:00
Râu Cao d5edb93ec5 Use new hubot URL 2023-08-14 17:50:47 +02:00
Râu Cao 0ecc4079c2 Ember code updates 2023-08-14 17:50:10 +02:00
Râu Cao 3f080cf553 Open external link in new tab
closes #170
2023-02-02 12:32:50 +08:00
Râu Cao 0bdb4d2021 Connect wallet and fill in address during signup
In case there's a wallet available, ask the user to connect it and fill
in their account address in the form field.

Also, check the status code when submitting it to the back-end and show
an error message instead of the success/completion page in case the
contributor could not be created.
2023-01-21 12:39:14 +08:00
Râu Cao 1e7d8491f9 Refactor wallet connect, automatically propose network 2023-01-20 17:29:20 +08:00
Râu Cao 4356288497 Rename signup route, address property 2023-01-20 17:29:20 +08:00
Râu Cao 87bf8ff2df Inspect user txs on RSK explorer 2023-01-20 17:29:20 +08:00
Râu Cao 26975636a0 Update README 2023-01-20 17:29:17 +08:00
Râu Cao dad56a7b31 Merge pull request #212 from 67P/bugfix/load_contributor
Fix dashboard hanging when loading app from contributor URL
2023-01-20 17:29:05 +08:00
Râu Cao d069fa02ab Add links 2023-01-15 14:41:25 +08:00
Râu Cao d219abc358 RSK -> Rootstock 2023-01-15 14:39:54 +08:00
Râu Cao 13989bf973 Update README
refs #18
2023-01-15 14:38:43 +08:00
Râu Cao b3ab69d599 Update CI badge in README 2023-01-15 14:28:19 +08:00
Râu Cao fe3816501c Fix dashboard hanging when loading app from contributor URL
Loading the app from /dashboard/contributors/:id is failing, because the
contributors are loaded later. This fetches the requested contributor
separately when launching the app from a contributor profile URL.
2023-01-15 14:24:51 +08:00
Râu Cao 854402aa73 Merge pull request #213 from 67P/feature/github_actions_ci
Switch from Travis CI to GitHub Actions
2023-01-15 14:24:14 +08:00
Râu Cao abc19c715b Fix double builds in CI 2023-01-15 14:20:48 +08:00
Râu Cao b6434ac8d2 Remove Travis config 2023-01-15 14:17:30 +08:00
Râu Cao 4788b8c51c Add GitHub Actions CI config 2023-01-15 14:17:09 +08:00
32 changed files with 710 additions and 468 deletions
+30
View File
@@ -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
-21
View File
@@ -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
+12 -6
View File
@@ -1,9 +1,15 @@
[![Build Status](https://travis-ci.org/67P/kredits-web.svg?branch=master)](https://travis-ci.org/67P/kredits-web) [![Build Status](https://github.com/67P/kredits-web/actions/workflows/ci.yml/badge.svg)](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. This is the main Web UI for Kosmos [Kredits](https://wiki.kosmos.org/Kredits).
A short introduction of this app could easily go here. 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 ## Development
@@ -24,7 +30,7 @@ You will need the following things properly installed on your computer.
### Building/running for development ### 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 app at [http://localhost:4200](http://localhost:4200).
* Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). * 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 ### Building
* `ember build` (development) * `ember build` (development)
* `ember build --environment production` (production) * `npm run build` (production)
### Deploying ### Deploying
+1 -1
View File
@@ -1,6 +1,6 @@
<form onsubmit={{action "submit"}}> <form onsubmit={{action "submit"}}>
<p> <p>
<label for="c-account">Ethereum account</label> <label for="c-account">Rootstock account</label>
<Input @type="text" <Input @type="text"
@value={{this.account}} @value={{this.account}}
name="account" id="c-account" name="account" id="c-account"
@@ -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 { inject as service } from '@ember/service';
import { computed } from '@ember/object'; import { action } from '@ember/object';
import { isPresent } from '@ember/utils'; 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(), get userHasWallet () {
router: service(),
setupInProgress: false,
userHasEthereumWallet: computed(function() {
return isPresent(window.ethereum); return isPresent(window.ethereum);
}),
showConnectButton: computed('userHasEthereumWallet',
'kredits.hasAccounts', function() {
return this.userHasEthereumWallet &&
!this.kredits.hasAccounts;
}),
actions: {
signup() {
this.router.transitionTo('signup');
},
async connectAccount() {
try {
await window.ethereum.enable();
this.set('setupInProgress', true);
await this.kredits.setup();
this.set('setupInProgress', false);
} catch (error) {
this.set('setupInProgress', false);
console.log('Opening Ethereum wallet failed:', error);
}
}
} }
}); get walletConnected () {
return this.userHasWallet && this.kredits.hasAccounts;
}
get walletDisconnected () {
return this.userHasWallet && !this.kredits.hasAccounts;
}
@action
signup () {
this.router.transitionTo('signup');
}
@action
async connectWallet () {
this.setupInProgress = true;
await this.kredits.connectWallet();
this.setupInProgress = false;
}
}
@@ -2,16 +2,15 @@
{{#if this.setupInProgress}} {{#if this.setupInProgress}}
Connecting account... Connecting account...
{{else}} {{else}}
{{#if (and this.kredits.hasAccounts this.kredits.currentUser)}} {{#if (and this.walletConnected this.kredits.currentUser)}}
{{this.kredits.currentUser.name}} {{this.kredits.currentUser.name}}
{{#if this.kredits.currentUserIsCore}}
<span class="core-flag">(core)</span>
{{/if}}
{{else}} {{else}}
<span class="anonymous">Anonymous</span> <span class="anonymous">Anonymous</span>
<button onclick={{action "signup"}} class="small" type="button">Sign up</button> <button onclick={{action "signup"}} id="signup"
{{#if this.showConnectButton}} class="small" type="button">Sign up</button>
<button onclick={{action "connectAccount"}} class="small green" type="button">Connect account</button> {{#if this.walletDisconnected}}
<button onclick={{action "connectWallet"}} id="connect"
class="small green" type="button">Connect wallet</button>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/if}} {{/if}}
+50
View File
@@ -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');
}
})
}
}
-49
View File
@@ -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');
});
}
}
});
+6 -8
View File
@@ -1,14 +1,12 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { action } from '@ember/object';
import config from 'kredits-web/config/environment'; import config from 'kredits-web/config/environment';
export default Controller.extend({ export default class IndexController extends Controller {
actions: {
connectGithub () {
window.location = config.githubConnectUrl;
}
@action
connectGithub () {
window.location = config.githubConnectUrl;
} }
}); }
+1 -1
View File
@@ -26,7 +26,7 @@ Router.map(function() {
}); });
this.route('signup', function() { this.route('signup', function() {
this.route('github'); this.route('github');
this.route('eth-account'); this.route('account');
this.route('complete'); this.route('complete');
}); });
this.route('budget', function() { this.route('budget', function() {
+7 -1
View File
@@ -8,7 +8,13 @@ export default Route.extend({
contributors: alias('kredits.contributors'), contributors: alias('kredits.contributors'),
model (params) { 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) { setupController (controller, model) {
+23
View File
@@ -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');
}
}
}
-17
View File
@@ -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');
}
}
});
+1 -1
View File
@@ -22,7 +22,7 @@ export default Route.extend({
this.kredits.set('githubAccessToken', accessToken); this.kredits.set('githubAccessToken', accessToken);
this.transitionTo('signup.eth-account'); this.transitionTo('signup.account');
} }
}); });
+2 -2
View File
@@ -4,8 +4,8 @@ import config from 'kredits-web/config/environment';
function createStore(name) { function createStore(name) {
let networkName; let networkName;
if (config.web3RequiredNetworkName) { if (config.web3NetworkName) {
networkName = config.web3RequiredNetworkName.toLocaleLowerCase().replace(' ', '-'); networkName = config.web3NetworkName.toLocaleLowerCase().replace(' ', '-');
} else { } else {
networkName = 'custom'; networkName = 'custom';
} }
+52 -24
View File
@@ -5,7 +5,7 @@ import Service from '@ember/service';
import EmberObject from '@ember/object'; import EmberObject from '@ember/object';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { alias, filterBy, notEmpty, sort } from '@ember/object/computed'; 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 { inject as service } from '@ember/service';
import { task, taskGroup } from 'ember-concurrency'; 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 processContributionData from 'kredits-web/utils/process-contribution-data';
import processReimbursementData from 'kredits-web/utils/process-reimbursement-data'; import processReimbursementData from 'kredits-web/utils/process-reimbursement-data';
import formatKredits from 'kredits-web/utils/format-kredits'; import formatKredits from 'kredits-web/utils/format-kredits';
import switchNetwork from 'kredits-web/utils/switch-network';
import config from 'kredits-web/config/environment'; import config from 'kredits-web/config/environment';
import Contributor from 'kredits-web/models/contributor'; import Contributor from 'kredits-web/models/contributor';
@@ -55,6 +56,21 @@ export default Service.extend({
this.set('contributors', []); this.set('contributors', []);
this.set('contributions', []); this.set('contributions', []);
this.set('reimbursements', []); 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 // This is called in the application route's beforeModel(). So it is
@@ -64,7 +80,7 @@ export default Service.extend({
let ethProvider; let ethProvider;
return new Promise(resolve => { return new Promise(resolve => {
function instantiateWithoutAccount () { function instantiateWithoutWallet () {
console.debug('[kredits] Creating new instance from npm module class'); console.debug('[kredits] Creating new instance from npm module class');
console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`); console.debug(`[kredits] providerURL: ${config.web3ProviderUrl}`);
ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl); ethProvider = new ethers.providers.JsonRpcProvider(config.web3ProviderUrl);
@@ -74,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'); console.debug('[kredits] Using user-provided Web3 instance, e.g. from Metamask');
ethProvider = new ethers.providers.Web3Provider(web3Provider); ethProvider = new ethers.providers.Web3Provider(web3Provider);
const network = await ethProvider.getNetwork();
const accounts = await ethProvider.listAccounts();
const chainId = config.web3ChainId;
const network = await ethProvider.getNetwork(); if (isEmpty(accounts)) return instantiateWithoutWallet();
if (isPresent(config.web3RequiredChainId) &&
network.chainId !== config.web3RequiredChainId) {
return instantiateWithoutAccount();
}
ethProvider.listAccounts().then(accounts => { if (network.chainId !== chainId) {
return switchNetwork();
} else {
context.set('currentUserAccounts', accounts); context.set('currentUserAccounts', accounts);
const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner(); const ethSigner = accounts.length === 0 ? null : ethProvider.getSigner();
resolve({ resolve({
ethProvider, ethProvider,
ethSigner ethSigner
}); });
}); }
} }
if (window.ethereum) { if (window.ethereum) {
if (window.ethereum.isConnected()) { instantiateWithWallet(window.ethereum, this);
instantiateWithAccount(window.ethereum, this); } else {
} else { instantiateWithoutWallet();
instantiateWithoutAccount();
}
}
// Legacy dapp browsers...
else if (window.web3) {
instantiateWithAccount(window.web3.currentProvider, this);
}
// Non-dapp browsers...
else {
instantiateWithoutAccount();
} }
}); });
}, },
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 () { async setup () {
const kredits = await this.getEthProvider().then(providerAndSigner => { const kredits = await this.getEthProvider().then(providerAndSigner => {
return new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, { return new Kredits(providerAndSigner.ethProvider, providerAndSigner.ethSigner, {
@@ -274,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 () { fetchContributors () {
console.debug(`[kredits] Fetching all contributors from the network`); console.debug(`[kredits] Fetching all contributors from the network`);
return this.kredits.Contributor.all() return this.kredits.Contributor.all()
+30 -26
View File
@@ -45,14 +45,16 @@
</div> </div>
</section> </section>
<section id="contributions-by-type"> {{#if this.contributionsConfirmed}}
<header> <section id="contributions-by-type">
<h2>Contributions by type</h2> <header>
</header> <h2>Contributions by type</h2>
<div class="content"> </header>
<ChartContributionsByType @contributions={{this.contributions}} /> <div class="content">
</div> <ChartContributionsByType @contributions={{this.contributions}} />
</section> </div>
</section>
{{/if}}
</div> </div>
<div id="contributions"> <div id="contributions">
@@ -87,24 +89,26 @@
</section> </section>
{{/if}} {{/if}}
<section id="contributions-confirmed"> {{#if this.contributionsConfirmed}}
<header class="with-nav"> <section id="contributions-confirmed">
<h2>Confirmed Contributions</h2> <header class="with-nav">
<nav> <h2>Confirmed Contributions</h2>
<button type="button" <nav>
onclick={{action "toggleQuickFilterConfirmed"}} <button type="button"
class="small {{if this.showQuickFilterConfirmed "active"}}"> onclick={{action "toggleQuickFilterConfirmed"}}
filter class="small {{if this.showQuickFilterConfirmed "active"}}">
</button> filter
</nav> </button>
</header> </nav>
<div class="content"> </header>
<ContributionList @contributions={{this.contributionsConfirmedSorted}} <div class="content">
@vetoContribution={{action "vetoContribution"}} <ContributionList @contributions={{this.contributionsConfirmedSorted}}
@selectedContributionId={{this.selectedContributionId}} @vetoContribution={{action "vetoContribution"}}
@showQuickFilter={{this.showQuickFilterConfirmed}} /> @selectedContributionId={{this.selectedContributionId}}
</div> @showQuickFilter={{this.showQuickFilterConfirmed}} />
</section> </div>
</section>
{{/if}}
{{#if this.showFullContributionSync}} {{#if this.showFullContributionSync}}
<section id="sync-all-contributions"> <section id="sync-all-contributions">
@@ -31,7 +31,7 @@
<div class="actions"> <div class="actions">
<p> <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}} {{#if this.model.ipfsHash}}
<a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect IPFS profile</a> <a href="{{this.ipfsGatewayUrl}}/{{this.model.ipfsHash}}" class="button small" target="_blank" rel="noopener noreferrer">Inspect IPFS profile</a>
{{/if}} {{/if}}
+30
View File
@@ -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>
+2 -1
View File
@@ -7,7 +7,8 @@
</p> </p>
<p> <p>
Why not say hi to your fellow contributors 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>
<p class="actions"> <p class="actions">
<LinkTo @route="dashboard" class="button small">Return to dashboard</LinkTo> <LinkTo @route="dashboard" class="button small">Return to dashboard</LinkTo>
-24
View File
@@ -1,24 +0,0 @@
<header>
<h2>Complete your contributor profile</h2>
</header>
<div class="content text-lg">
<p class="mb-8">
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>
+29
View File
@@ -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
View File
@@ -26,11 +26,11 @@ module.exports = function(environment) {
}, },
web3ProviderUrl: 'https://rsk-testnet.kosmos.org', web3ProviderUrl: 'https://rsk-testnet.kosmos.org',
web3RequiredChainId: 31, web3ChainId: 31,
web3RequiredNetworkName: 'RSK Testnet', web3NetworkName: 'RSK Testnet',
githubConnectUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/connect/github', githubConnectUrl: 'https://hal8000.kosmos.chat/kredits/signup/connect/github',
githubSignupUrl: 'https://hal8000.chat.kosmos.org/kredits/signup/github', githubSignupUrl: 'https://hal8000.kosmos.chat/kredits/signup/github',
ipfs: { ipfs: {
host: 'ipfs.kosmos.org', host: 'ipfs.kosmos.org',
@@ -82,7 +82,12 @@ module.exports = function(environment) {
if (process.env.WEB3_PROVIDER_URL) { if (process.env.WEB3_PROVIDER_URL) {
ENV.web3ProviderUrl = 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; return ENV;
+9 -9
View File
@@ -1,12 +1,12 @@
{ {
"name": "kredits-web", "name": "kredits-web",
"version": "2.0.0-beta.3", "version": "2.0.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "kredits-web", "name": "kredits-web",
"version": "2.0.0-beta.3", "version": "2.0.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.19.1", "@babel/eslint-parser": "^7.19.1",
@@ -14,7 +14,7 @@
"@ember/optional-features": "^1.3.0", "@ember/optional-features": "^1.3.0",
"@glimmer/component": "^1.0.0", "@glimmer/component": "^1.0.0",
"@glimmer/tracking": "^1.0.0", "@glimmer/tracking": "^1.0.0",
"@kredits/contracts": "^7.0.1", "@kredits/contracts": "^7.3.0",
"babel-preset-es2015": "^6.22.0", "babel-preset-es2015": "^6.22.0",
"broccoli-asset-rev": "^3.0.0", "broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^1.12.1", "ember-auto-import": "^1.12.1",
@@ -3067,9 +3067,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@kredits/contracts": { "node_modules/@kredits/contracts": {
"version": "7.0.1", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.3.0.tgz",
"integrity": "sha512-4IhNQoPc1BCT63NKyDOmV+ryb82FKNbIqfDyTdant6WTI9lfkIAnI8Et+ynQmqxx3Efez5hqoDZtEhCijcuIng==", "integrity": "sha512-7Bo2LFEqGIK39G54AStas1RWouBOp+r7Vcv2vP1uLbY01Usea8SHvd2OjLP6nFgsQa0n/rto9oremHv5IAylrQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@kosmos/schemas": "^3.1.0", "@kosmos/schemas": "^3.1.0",
@@ -26066,9 +26066,9 @@
"dev": true "dev": true
}, },
"@kredits/contracts": { "@kredits/contracts": {
"version": "7.0.1", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@kredits/contracts/-/contracts-7.3.0.tgz",
"integrity": "sha512-4IhNQoPc1BCT63NKyDOmV+ryb82FKNbIqfDyTdant6WTI9lfkIAnI8Et+ynQmqxx3Efez5hqoDZtEhCijcuIng==", "integrity": "sha512-7Bo2LFEqGIK39G54AStas1RWouBOp+r7Vcv2vP1uLbY01Usea8SHvd2OjLP6nFgsQa0n/rto9oremHv5IAylrQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@kosmos/schemas": "^3.1.0", "@kosmos/schemas": "^3.1.0",
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "kredits-web", "name": "kredits-web",
"version": "2.0.0-beta.3", "version": "2.0.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",
@@ -17,7 +17,7 @@
"start": "ember serve", "start": "ember serve",
"test": "npm-run-all lint:* test:*", "test": "npm-run-all lint:* test:*",
"test:ember": "ember 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": "ember build --environment=production",
"build-prod": "rm -rf release/* && ember build -prod --output-path release", "build-prod": "rm -rf release/* && ember build -prod --output-path release",
"preversion": "npm test", "preversion": "npm test",
@@ -30,7 +30,7 @@
"@ember/optional-features": "^1.3.0", "@ember/optional-features": "^1.3.0",
"@glimmer/component": "^1.0.0", "@glimmer/component": "^1.0.0",
"@glimmer/tracking": "^1.0.0", "@glimmer/tracking": "^1.0.0",
"@kredits/contracts": "^7.0.1", "@kredits/contracts": "^7.3.0",
"babel-preset-es2015": "^6.22.0", "babel-preset-es2015": "^6.22.0",
"broccoli-asset-rev": "^3.0.0", "broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^1.12.1", "ember-auto-import": "^1.12.1",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -8,7 +8,7 @@
<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-beta.3%2Bd90ddd46%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/vendor-ca96b2e19fc9f356e3dbad90c9f3f323.css">
<link integrity="" rel="stylesheet" href="/assets/kredits-web-f21602587acec9a1744c244385a83592.css"> <link integrity="" rel="stylesheet" href="/assets/kredits-web-f21602587acec9a1744c244385a83592.css">
@@ -24,8 +24,8 @@
<body> <body>
<script src="/assets/vendor-4ee536ded971436a83d35253df25f2cb.js" integrity="sha256-yb+pC9iuZ8WcQwk9VuDNhSlWguL7BEABsEOEhBpwUC0= sha512-50LApx5XqeKHu/FMaljzqhAMPgkqSiy2rDhUcViYBcXWBBYmilFirup/iWvLiNCdfr5ct9wk7+tU9iShdSgDww==" ></script> <script src="/assets/vendor-4d87b3e0995c5bb18e46836a089900a4.js" integrity="sha256-5WmWY05Kd+n98Sw0GwuaWodmDVBZ2yVGGtC/2owVfQE= sha512-tiGPA5zvfhnSC9LPTmpvXJmOu30yhsdPqSiZJMPGmE/ez/xJLMaM29vjs4Cs74cDf6fX4uYSGRIu1KfOyGpJYA==" ></script>
<script src="/assets/kredits-web-89113e4d5c371829e3d1b05e86cbab9b.js" integrity="sha256-aabl6JZt69T2BZIv4yXcRFfmhWCyEhVA2V4g1ZzkZ6s= sha512-aD0s+kWNsXdHJFxIkgncvwNZVhtARosjjKnbLE0w1ihSrvRoptWXV1SgySdZIHKxFgbNmJGA2oQOzhBKIoqvVg==" ></script> <script src="/assets/kredits-web-ae44ce0c1a892c2da424f8002b037b86.js" integrity="sha256-ZT6R9QwhnMEgs1X3pmlX6Oh5N4zJgXWLF8al8PTOKqk= sha512-BYrlNe9cti4ZEcp//b5FhPGvmAE08vTEC3WT15EpTqTXccNkOqFJuiGob+ymxIDfDzVU6vN8szxCTBOUe61Tmw==" ></script>
</body> </body>
@@ -3,37 +3,90 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers'; import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; 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) { module('Integration | Component | topbar-account-panel', function(hooks) {
setupRenderingTest(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 />`); 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'); let service = this.owner.lookup('service:kredits');
service.set('currentUserAccounts', [{ foo: 'bar' }]); service.set('currentUserAccounts', [{ foo: 'bar' }]);
await render(hbs`<TopbarAccountPanel />`); 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'); let service = this.owner.lookup('service:kredits');
service.set('currentUserAccounts', [{ foo: 'bar' }]); service.set('currentUserAccounts', [{ foo: 'bar' }]);
service.set('currentUser', { service.set('currentUser', { name: 'Dorian Nakamoto', isCore: false });
name: 'Dorian Nakamoto',
isCore: false
});
await render(hbs`<TopbarAccountPanel />`); await render(hbs`<TopbarAccountPanel />`);
assert.dom(this.element).hasText('Dorian Nakamoto'); assert.dom(this.element).hasText(
'Dorian Nakamoto', 'shows current user\'s name'
service.set('currentUser.isCore', true); );
await render(hbs`<TopbarAccountPanel />`); assert.equal(
this.element.querySelectorAll('button#signup').length, 0,
assert.equal(this.element.querySelectorAll('span.core-flag').length, 1); 'signup button is not visible'
);
assert.equal(
this.element.querySelectorAll('button#connect').length, 0,
'connect button is not visible'
);
}); });
}); });
@@ -1,11 +1,11 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit'; import { setupTest } from 'ember-qunit';
module('Unit | Route | signup/eth-account', function(hooks) { module('Unit | Route | signup/account', function(hooks) {
setupTest(hooks); setupTest(hooks);
test('it exists', function(assert) { 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); assert.ok(route);
}); });
}); });