4 Commits

Author SHA1 Message Date
Spencer Ofwiti
c83cc5240e Merge remote-tracking branch 'origin/bvander/accounts-search-review' into bvander/accounts-search-review
# Conflicts:
#	src/app/_services/user.service.ts
#	src/app/pages/accounts/accounts.component.ts
2021-05-09 20:08:39 +03:00
5061dc0d32 still kinda broken 2021-05-09 10:07:49 -07:00
6f26793359 more changes 2021-05-09 09:05:36 -07:00
e5fe424185 refactor based on review 2021-05-08 12:12:33 -07:00
178 changed files with 2875 additions and 75150 deletions

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,30 +0,0 @@
#!/bin/sh
if [ -z "$husky_skip_init" ]; then
debug () {
[ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1"
}
readonly hook_name="$(basename "$0")"
debug "starting $hook_name..."
if [ "$HUSKY" = "0" ]; then
debug "HUSKY env variable is set to 0, skipping hook"
exit 0
fi
if [ -f ~/.huskyrc ]; then
debug "sourcing ~/.huskyrc"
. ~/.huskyrc
fi
export readonly husky_skip_init=1
sh -e "$0" "$@"
exitCode="$?"
if [ $exitCode != 0 ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)"
exit $exitCode
fi
exit 0
fi

View File

@@ -1,4 +0,0 @@
package.json
package-lock.json
yarn.lock
dist

View File

@@ -1,9 +0,0 @@
{
"useTabs": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"semi": true,
"bracketSpacing": true,
"arrowParens": "always"
}

View File

@@ -10,9 +10,7 @@ Run `npm install -g @angular/cli` to install the angular CLI.
## Development server ## Development server
Run `ng serve` for a local server, `npm run start:dev` for a dev server and `npm run start:prod` for a prod server.. Run `npm run start:dev` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding ## Code scaffolding
@@ -24,21 +22,14 @@ Run `ng generate module module-name --route module-name --module app.module` to
## Build ## Build
Run `ng build` to build the project using local configurations. set you environment variables - set these via environment variables as found in set-env.ts
The build artifacts will be stored in the `dist/` directory. // TODO create a .env file so people don't have to set these one-by-one
Use the `npm run build:dev` script for a development build and the `npm run build:prod` script for a production build. Run `npm run build:dev` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `build:prod` script for a production build.
## PWA
The app supports Progressive Web App capabilities.
Run `npm run start:pwa` to run the project in PWA mode.
PWA mode works using production configurations.
## Running unit tests ## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). Run `npm run test:dev` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests ## Running end-to-end tests
@@ -46,19 +37,11 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac
## Environment variables ## Environment variables
Default environment variables are located in the `src/environments/` directory. Environment variables are contained in the `.env` file. See `.env.example` for a template.
Custom environment variables are contained in the `.env` file. See `.env.example` for a template.
Custom environment variables are set via the `set-env.ts` file. Default environment variables are set in the `set-env.ts` file.
Once loaded they will be populated in the directory `src/environments/`. Once loaded they will be populated in the directory `src/environments/`.
It contains environment variables for development on `environment.dev.ts` and production on `environment.prod.ts`. It contains environment variables for development on `environment.ts` and production on `environment.prod.ts`.
## Code formatting
The system has automated code formatting using [Prettier](https://prettier.io/) and [TsLint](https://palantir.github.io/tslint/).
To view the styling rules set, check out `.prettierrc` and `tslint.json`.
Run `npm run format:lint` To perform formatting and linting of the codebase.
## Further help ## Further help

View File

@@ -26,17 +26,19 @@
"aot": true, "aot": true,
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets", "src/assets"
"src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss", "src/styles.scss",
"node_modules/datatables.net-dt/css/jquery.dataTables.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css" "node_modules/bootstrap/dist/css/bootstrap.min.css"
], ],
"scripts": [ "scripts": [
"node_modules/jquery/dist/jquery.js", "node_modules/jquery/dist/jquery.js",
"node_modules/bootstrap/dist/js/bootstrap.js" "node_modules/datatables.net/js/jquery.dataTables.js",
"node_modules/bootstrap/dist/js/bootstrap.js",
"node_modules/block-syncer/dist/worker_ondemand.js"
] ]
}, },
"configurations": { "configurations": {
@@ -66,9 +68,7 @@
"maximumWarning": "6kb", "maximumWarning": "6kb",
"maximumError": "10kb" "maximumError": "10kb"
} }
], ]
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
}, },
"dev": { "dev": {
"fileReplacements": [ "fileReplacements": [
@@ -110,8 +110,7 @@
"codeCoverage": true, "codeCoverage": true,
"assets": [ "assets": [
"src/favicon.ico", "src/favicon.ico",
"src/assets", "src/assets"
"src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",

View File

@@ -35,7 +35,7 @@ http {
location / { location / {
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.htm; index index.html index.htm;
try_files $uri $uri/ /index.html =404; try_files $uri $uri/ /index.html;
} }
} }
} }

View File

@@ -1,31 +0,0 @@
{
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico",
"/index.html",
"/manifest.webmanifest",
"/*.css",
"/*.js",
"/assets/*.png"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
]
}
}
]
}

21803
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,17 +8,10 @@
"start:prod": "ng serve --prod", "start:prod": "ng serve --prod",
"build:dev": "ng build -c dev", "build:dev": "ng build -c dev",
"build:prod": "ng build --prod", "build:prod": "ng build --prod",
"start:pwa": "npm run build:prod && http-server -p 4200 dist/cic-staff-client", "test:dev": "ng test",
"test": "ng test",
"format:check": "prettier --config ./.prettierrc --list-different \"src/{app,environments,assets}/**/*.{ts,js,json,css,scss}\"",
"format:refactor": "prettier --config ./.prettierrc --write \"src/{app,environments,assets}/**/*.{ts,js,json,css,scss}\"",
"format:fix": "pretty-quick --staged",
"format:lint": "npm run format:refactor && npm run lint",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"precommit": "npm run format:fix && npm run lint", "postinstall": "node patch-webpack.js"
"postinstall": "node patch-webpack.js",
"prepare": "husky install"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@@ -32,20 +25,29 @@
"@angular/platform-browser": "~10.2.0", "@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0", "@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0", "@angular/router": "~10.2.0",
"@angular/service-worker": "~10.2.0",
"@cicnet/schemas-data-validator": "*",
"@popperjs/core": "^2.5.4", "@popperjs/core": "^2.5.4",
"angular-datatables": "^9.0.2",
"block-syncer": "^0.2.4",
"bootstrap": "^4.5.3", "bootstrap": "^4.5.3",
"chart.js": "^2.9.4",
"cic-client": "0.1.4", "cic-client": "0.1.4",
"cic-client-meta": "0.0.7-alpha.6", "cic-client-meta": "0.0.7-alpha.6",
"cic-schemas-data-validator": "^1.0.0-alpha.3",
"datatables.net": "^1.10.22",
"datatables.net-dt": "^1.10.22",
"ethers": "^5.0.31", "ethers": "^5.0.31",
"http-server": "^0.12.3",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"mocha": "^8.2.1",
"moolb": "^0.1.0",
"ng2-charts": "^2.4.2",
"ngx-logger": "^4.2.1", "ngx-logger": "^4.2.1",
"openpgp": "^4.10.10",
"popper.js": "^1.16.1",
"rxjs": "~6.6.0", "rxjs": "~6.6.0",
"sha3": "^2.1.4", "sha3": "^2.1.4",
"tslib": "^2.0.0", "tslib": "^2.0.0",
"vcard-parser": "^1.0.0", "vcard-parser": "^1.0.0",
"vcards-js": "^2.10.0",
"web3": "^1.3.0", "web3": "^1.3.0",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"
}, },
@@ -60,7 +62,6 @@
"@types/node": "^12.20.6", "@types/node": "^12.20.6",
"codelyzer": "^6.0.0", "codelyzer": "^6.0.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"husky": "^6.0.0",
"jasmine-core": "~3.6.0", "jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0", "jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0", "karma": "~5.0.0",
@@ -69,21 +70,11 @@
"karma-jasmine": "~4.0.0", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0", "karma-jasmine-html-reporter": "^1.5.0",
"karma-junit-reporter": "^2.0.1", "karma-junit-reporter": "^2.0.1",
"prettier": "^2.3.0",
"pretty-quick": "^3.1.0",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"secp256k1": "^4.0.2", "secp256k1": "^4.0.2",
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"tslint": "~6.1.0", "tslint": "~6.1.0",
"tslint-angular": "^3.0.3",
"tslint-config-prettier": "^1.18.0",
"tslint-jasmine-rules": "^1.6.1",
"typescript": "~4.0.2", "typescript": "~4.0.2",
"yargs": "^13.3.2" "yargs": "^13.3.2"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged & ng lint"
}
} }
} }

View File

@@ -6,7 +6,7 @@ require('dotenv').config();
const environment = argv.environment; const environment = argv.environment;
const isProduction = environment === 'prod'; const isProduction = environment === 'prod';
const targetPath = isProduction ? `./src/environments/environment.prod.ts` : `./src/environments/environment.dev.ts`; const targetPath = isProduction ? `./src/environments/environment.prod.ts` : `./src/environments/environment.ts`;
const environmentVars = `import {NgxLoggerLevel} from 'ngx-logger'; const environmentVars = `import {NgxLoggerLevel} from 'ngx-logger';
@@ -15,13 +15,13 @@ export const environment = {
bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996}, bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996},
logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'}, logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'},
serverLogLevel: ${process.env.SERVER_LOG_LEVEL || 'NgxLoggerLevel.OFF'}, serverLogLevel: ${process.env.SERVER_LOG_LEVEL || 'NgxLoggerLevel.OFF'},
loggingUrl: '${process.env.CIC_LOGGING_URL || ''}', loggingUrl: '${process.env.CIC_LOGGING_URL || 'http://localhost:8000'}',
cicMetaUrl: '${process.env.CIC_META_URL || 'https://meta.dev.grassrootseconomics.net'}', cicMetaUrl: '${process.env.CIC_META_URL || 'https://meta.dev.grassrootseconomics.net'}',
publicKeysUrl: '${process.env.CIC_KEYS_URL || 'https://dev.grassrootseconomics.net/.well-known/publickeys'}', publicKeysUrl: '${process.env.CIC_KEYS_URL || 'http://localhost:8000/keys.asc'}',
cicCacheUrl: '${process.env.CIC_CACHE_URL || 'https://cache.dev.grassrootseconomics.net'}', cicCacheUrl: '${process.env.CIC_CACHE_URL || 'https://cache.dev.grassrootseconomics.net'}',
web3Provider: '${process.env.CIC_WEB3_PROVIDER || 'wss://bloxberg-ws.dev.grassrootseconomics.net'}', web3Provider: '${process.env.CIC_WEB3_PROVIDER || 'ws://localhost:63546'}',
cicUssdUrl: '${process.env.CIC_USSD_URL || 'https://ussd.dev.grassrootseconomics.net'}', cicUssdUrl: '${process.env.CIC_USSD_URL || 'https://ussd.dev.grassrootseconomics.net'}',
registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0xea6225212005e86a4490018ded4bf37f3e772161'}', registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0x6Ca3cB14aA6F761712E1C18646AfBA4d5Ae249E8'}',
trustedDeclaratorAddress: '${process.env.CIC_TRUSTED_ADDRESS || '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C'}' trustedDeclaratorAddress: '${process.env.CIC_TRUSTED_ADDRESS || '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C'}'
}; };
`; `;

View File

@@ -1,7 +1,7 @@
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import Web3 from 'web3'; import Web3 from 'web3';
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountsIndex.json'); const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountRegistry.json');
const web3: Web3 = new Web3(environment.web3Provider); const web3: Web3 = new Web3(environment.web3Provider);
export class AccountIndex { export class AccountIndex {
@@ -20,30 +20,30 @@ export class AccountIndex {
} }
public async totalAccounts(): Promise<number> { public async totalAccounts(): Promise<number> {
return await this.contract.methods.entryCount().call(); return await this.contract.methods.count().call();
} }
public async haveAccount(address: string): Promise<boolean> { public async haveAccount(address: string): Promise<boolean> {
return (await this.contract.methods.have(address).call()) !== 0; return await this.contract.methods.accountIndex(address).call() !== 0;
} }
public async last(numberOfAccounts: number): Promise<Array<string>> { public async last(numberOfAccounts: number): Promise<Array<string>> {
const count: number = await this.totalAccounts(); const count: number = await this.totalAccounts();
let lowest: number = count - numberOfAccounts; let lowest: number = count - numberOfAccounts - 1;
if (lowest < 0) { if (lowest < 0) {
lowest = 0; lowest = 0;
} }
const accounts: Array<string> = []; const accounts: Array<string> = [];
for (let i = count - 1; i >= lowest; i--) { for (let i = count - 1; i > lowest; i--) {
const account: string = await this.contract.methods.entry(i).call(); const account: string = await this.contract.methods.accounts(i).call();
accounts.push(account); accounts.push(account);
} }
return accounts; return accounts;
} }
public async addToAccountRegistry(address: string): Promise<boolean> { public async addToAccountRegistry(address: string): Promise<boolean> {
if (!(await this.haveAccount(address))) { if (!await this.haveAccount(address)) {
return await this.contract.methods.add(address).send({ from: this.signerAddress }); return await this.contract.methods.add(address).send({from: this.signerAddress});
} }
return true; return true;
} }

View File

@@ -1,5 +1,5 @@
import { TokenRegistry } from '@app/_eth/token-registry'; import { TokenRegistry } from '@app/_eth/token-registry';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
describe('TokenRegistry', () => { describe('TokenRegistry', () => {
it('should create an instance', () => { it('should create an instance', () => {

View File

@@ -1,5 +1,5 @@
import Web3 from 'web3'; import Web3 from 'web3';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json'); const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json');
const web3: Web3 = new Web3(environment.web3Provider); const web3: Web3 = new Web3(environment.web3Provider);

View File

@@ -1,27 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree,
Router,
} from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private router: Router) {} constructor(private router: Router) {}
canActivate( canActivate(
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,
state: RouterStateSnapshot state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) { if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
return true; return true;
} }
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
return false; return false;
} }
} }

View File

@@ -1,23 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree,
Router,
} from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class RoleGuard implements CanActivate { export class RoleGuard implements CanActivate {
constructor(private router: Router) {} constructor(private router: Router) {}
canActivate( canActivate(
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,
state: RouterStateSnapshot state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const currentUser = JSON.parse(localStorage.getItem(atob('CICADA_USER'))); const currentUser = JSON.parse(localStorage.getItem(atob('CICADA_USER')));
if (currentUser) { if (currentUser) {
if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) { if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
@@ -27,7 +21,8 @@ export class RoleGuard implements CanActivate {
return true; return true;
} }
this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url } }); this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url }});
return false; return false;
} }
} }

View File

@@ -2,4 +2,6 @@ function arraySum(arr: Array<number>): number {
return arr.reduce((accumulator, current) => accumulator + current, 0); return arr.reduce((accumulator, current) => accumulator + current, 0);
} }
export { arraySum }; export {
arraySum
};

View File

@@ -32,7 +32,7 @@ function copyToClipboard(text: any): boolean {
// copy the text // copy the text
document.execCommand('copy'); document.execCommand('copy');
} catch (err) { } catch (err) {
window.alert('Your Browser Does not support this! Error : ' + err); window.alert('Your Browser Doesn\'t support this! Error : ' + err);
return false; return false;
} }
// remove the selection range (Chrome throws a warning if we don't.) // remove the selection range (Chrome throws a warning if we don't.)
@@ -48,4 +48,6 @@ function copyToClipboard(text: any): boolean {
return true; return true;
} }
export { copyToClipboard }; export {
copyToClipboard
};

View File

@@ -1,7 +1,7 @@
import { ErrorStateMatcher } from '@angular/material/core'; import {ErrorStateMatcher} from '@angular/material/core';
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
export class CustomErrorStateMatcher implements ErrorStateMatcher { export class CustomErrorStateMatcher implements ErrorStateMatcher{
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
const isSubmitted: boolean = form && form.submitted; const isSubmitted: boolean = form && form.submitted;
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted)); return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));

View File

@@ -1,4 +1,4 @@
import { AbstractControl, ValidationErrors } from '@angular/forms'; import {AbstractControl, ValidationErrors} from '@angular/forms';
export class CustomValidator { export class CustomValidator {
static passwordMatchValidator(control: AbstractControl): void { static passwordMatchValidator(control: AbstractControl): void {

View File

@@ -4,7 +4,7 @@ function exportCsv(arrayData: Array<any>, filename: string, delimiter: string =
return; return;
} }
let csv: string = Object.keys(arrayData[0]).join(delimiter) + '\n'; let csv: string = Object.keys(arrayData[0]).join(delimiter) + '\n';
arrayData.forEach((obj) => { arrayData.forEach(obj => {
const row: Array<any> = []; const row: Array<any> = [];
for (const key in obj) { for (const key in obj) {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) {
@@ -14,7 +14,7 @@ function exportCsv(arrayData: Array<any>, filename: string, delimiter: string =
csv += row.join(delimiter) + '\n'; csv += row.join(delimiter) + '\n';
}); });
const csvData: Blob = new Blob([csv], { type: 'text/csv' }); const csvData: Blob = new Blob([csv], {type: 'text/csv'});
const csvUrl: string = URL.createObjectURL(csvData); const csvUrl: string = URL.createObjectURL(csvData);
const downloadLink: HTMLAnchorElement = document.createElement('a'); const downloadLink: HTMLAnchorElement = document.createElement('a');
@@ -33,4 +33,6 @@ function removeSpecialChar(str: string): string {
return str.replace(/[^a-zA-Z0-9 ]/g, ''); return str.replace(/[^a-zA-Z0-9 ]/g, '');
} }
export { exportCsv }; export {
exportCsv
};

View File

@@ -1,7 +1,7 @@
import { ErrorHandler, Injectable } from '@angular/core'; import {ErrorHandler, Injectable} from '@angular/core';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { HttpErrorResponse } from '@angular/common/http'; import {HttpErrorResponse} from '@angular/common/http';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
// A generalized http response error // A generalized http response error
export class HttpError extends Error { export class HttpError extends Error {
@@ -17,7 +17,10 @@ export class HttpError extends Error {
export class GlobalErrorHandler extends ErrorHandler { export class GlobalErrorHandler extends ErrorHandler {
private sentencesForWarningLogging: Array<string> = []; private sentencesForWarningLogging: Array<string> = [];
constructor(private loggingService: LoggingService, private router: Router) { constructor(
private loggingService: LoggingService,
private router: Router
) {
super(); super();
} }
@@ -33,9 +36,9 @@ export class GlobalErrorHandler extends ErrorHandler {
const isWarning: boolean = this.isWarning(errorTraceString); const isWarning: boolean = this.isWarning(errorTraceString);
if (isWarning) { if (isWarning) {
this.loggingService.sendWarnLevelMessage(errorTraceString, { error }); this.loggingService.sendWarnLevelMessage(errorTraceString, {error});
} else { } else {
this.loggingService.sendErrorLevelMessage(errorTraceString, this, { error }); this.loggingService.sendErrorLevelMessage(errorTraceString, this, {error});
} }
throw error; throw error;
@@ -45,30 +48,14 @@ export class GlobalErrorHandler extends ErrorHandler {
const route: string = this.router.url; const route: string = this.router.url;
if (error instanceof HttpErrorResponse) { if (error instanceof HttpErrorResponse) {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${ `There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`,
(error as HttpErrorResponse).status this, {error});
}`,
this,
{ error }
);
} else if (error instanceof TypeError) { } else if (error instanceof TypeError) {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(`There was a Type error on route ${route}.\n${error.message}`, this, {error});
`There was a Type error on route ${route}.\n${error.message}`,
this,
{ error }
);
} else if (error instanceof Error) { } else if (error instanceof Error) {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(`There was a general error on route ${route}.\n${error.message}`, this, {error});
`There was a general error on route ${route}.\n${error.message}`,
this,
{ error }
);
} else { } else {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(`Nobody threw an error but something happened on route ${route}!`, this, {error});
`Nobody threw an error but something happened on route ${route}!`,
this,
{ error }
);
} }
} }
@@ -87,10 +74,3 @@ export class GlobalErrorHandler extends ErrorHandler {
return isWarning; return isWarning;
} }
} }
export function rejectBody(error): { status: any; statusText: any } {
return {
status: error.status,
statusText: error.statusText,
};
}

View File

@@ -1,17 +1,18 @@
import { rejectBody } from '@app/_helpers/global-error-handler';
function HttpGetter(): void {} function HttpGetter(): void {}
HttpGetter.prototype.get = (filename) => HttpGetter.prototype.get = filename => new Promise((resolve, reject) => {
new Promise((resolve, reject) => { const xhr: XMLHttpRequest = new XMLHttpRequest();
fetch(filename).then((response) => { xhr.addEventListener('load', (e) => {
if (response.ok) { if (xhr.status === 200) {
resolve(response.text()); resolve(xhr.responseText);
} else {
reject(rejectBody(response));
}
return; return;
}
reject('failed with status ' + xhr.status + ': ' + xhr.statusText);
}); });
}); xhr.open('GET', filename);
xhr.send();
});
export { HttpGetter }; export {
HttpGetter
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
const objCsv: { size: number; dataFile: any } = { const objCsv: { size: number, dataFile: any } = {
size: 0, size: 0,
dataFile: [], dataFile: []
}; };
function readCsv(input: any): Array<any> | void { function readCsv(input: any): Array<any> | void {
if (input.files && input.files[0]) { if (input.files && input.files[0]) {
const reader: FileReader = new FileReader(); const reader: FileReader = new FileReader();
reader.readAsBinaryString(input.files[0]); reader.readAsBinaryString(input.files[0]);
reader.onload = (event) => { reader.onload = event => {
objCsv.size = event.total; objCsv.size = event.total;
objCsv.dataFile = event.target.result; objCsv.dataFile = event.target.result;
return parseData(objCsv.dataFile); return parseData(objCsv.dataFile);
@@ -18,11 +18,13 @@ function readCsv(input: any): Array<any> | void {
function parseData(data: any): Array<any> { function parseData(data: any): Array<any> {
const csvData: Array<any> = []; const csvData: Array<any> = [];
const lineBreak: Array<any> = data.split('\n'); const lineBreak: Array<any> = data.split('\n');
lineBreak.forEach((res) => { lineBreak.forEach(res => {
csvData.push(res.split(',')); csvData.push(res.split(','));
}); });
console.table(csvData); console.table(csvData);
return csvData; return csvData;
} }
export { readCsv }; export {
readCsv
};

View File

@@ -1,10 +1,10 @@
import { validatePerson, validateVcard } from '@cicnet/schemas-data-validator'; import { validatePerson, validateVcard } from 'cic-schemas-data-validator';
async function personValidation(person: any): Promise<void> { async function personValidation(person: any): Promise<void> {
const personValidationErrors: any = await validatePerson(person); const personValidationErrors: any = await validatePerson(person);
if (personValidationErrors) { if (personValidationErrors) {
personValidationErrors.map((error) => console.error(`${error.message}`)); personValidationErrors.map(error => console.error(`${error.message}`));
} }
} }
@@ -12,8 +12,11 @@ async function vcardValidation(vcard: any): Promise<void> {
const vcardValidationErrors: any = await validateVcard(vcard); const vcardValidationErrors: any = await validateVcard(vcard);
if (vcardValidationErrors) { if (vcardValidationErrors) {
vcardValidationErrors.map((error) => console.error(`${error.message}`)); vcardValidationErrors.map(error => console.error(`${error.message}`));
} }
} }
export { personValidation, vcardValidation }; export {
personValidation,
vcardValidation,
};

View File

@@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { ErrorInterceptor } from '@app/_interceptors/error.interceptor'; import { ErrorInterceptor } from '@app/_interceptors/error.interceptor';
describe('ErrorInterceptor', () => { describe('ErrorInterceptor', () => {
beforeEach(() => beforeEach(() => TestBed.configureTestingModule({
TestBed.configureTestingModule({ providers: [
providers: [ErrorInterceptor], ErrorInterceptor
}) ]
); }));
it('should be created', () => { it('should be created', () => {
const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor); const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor);

View File

@@ -1,18 +1,18 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { import {
HttpRequest, HttpRequest,
HttpHandler, HttpHandler,
HttpEvent, HttpEvent,
HttpInterceptor, HttpInterceptor, HttpErrorResponse
HttpErrorResponse,
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable, throwError } from 'rxjs'; import {Observable, throwError} from 'rxjs';
import { catchError } from 'rxjs/operators'; import {catchError} from 'rxjs/operators';
import { ErrorDialogService, LoggingService } from '@app/_services'; import {ErrorDialogService, LoggingService} from '@app/_services';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
@Injectable() @Injectable()
export class ErrorInterceptor implements HttpInterceptor { export class ErrorInterceptor implements HttpInterceptor {
constructor( constructor(
private errorDialogService: ErrorDialogService, private errorDialogService: ErrorDialogService,
private loggingService: LoggingService, private loggingService: LoggingService,
@@ -29,11 +29,9 @@ export class ErrorInterceptor implements HttpInterceptor {
} else { } else {
// The backend returned an unsuccessful response code. // The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong. // The response body may contain clues as to what went wrong.
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify( errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(err.error)}`;
err.error
)}`;
} }
this.loggingService.sendErrorLevelMessage(errorMessage, this, { error: err }); this.loggingService.sendErrorLevelMessage(errorMessage, this, {error: err});
switch (err.status) { switch (err.status) {
case 401: // unauthorized case 401: // unauthorized
this.router.navigateByUrl('/auth').then(); this.router.navigateByUrl('/auth').then();

View File

@@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { HttpConfigInterceptor } from './http-config.interceptor'; import { HttpConfigInterceptor } from './http-config.interceptor';
describe('HttpConfigInterceptor', () => { describe('HttpConfigInterceptor', () => {
beforeEach(() => beforeEach(() => TestBed.configureTestingModule({
TestBed.configureTestingModule({ providers: [
providers: [HttpConfigInterceptor], HttpConfigInterceptor
}) ]
); }));
it('should be created', () => { it('should be created', () => {
const interceptor: HttpConfigInterceptor = TestBed.inject(HttpConfigInterceptor); const interceptor: HttpConfigInterceptor = TestBed.inject(HttpConfigInterceptor);

View File

@@ -1,9 +1,15 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Injectable() @Injectable()
export class HttpConfigInterceptor implements HttpInterceptor { export class HttpConfigInterceptor implements HttpInterceptor {
constructor() {} constructor() {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {

View File

@@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { LoggingInterceptor } from './logging.interceptor'; import { LoggingInterceptor } from './logging.interceptor';
describe('LoggingInterceptor', () => { describe('LoggingInterceptor', () => {
beforeEach(() => beforeEach(() => TestBed.configureTestingModule({
TestBed.configureTestingModule({ providers: [
providers: [LoggingInterceptor], LoggingInterceptor
}) ]
); }));
it('should be created', () => { it('should be created', () => {
const interceptor: LoggingInterceptor = TestBed.inject(LoggingInterceptor); const interceptor: LoggingInterceptor = TestBed.inject(LoggingInterceptor);

View File

@@ -4,15 +4,18 @@ import {
HttpHandler, HttpHandler,
HttpEvent, HttpEvent,
HttpInterceptor, HttpInterceptor,
HttpResponse, HttpResponse
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs'; import {Observable} from 'rxjs';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { finalize, tap } from 'rxjs/operators'; import {finalize, tap} from 'rxjs/operators';
@Injectable() @Injectable()
export class LoggingInterceptor implements HttpInterceptor { export class LoggingInterceptor implements HttpInterceptor {
constructor(private loggingService: LoggingService) {}
constructor(
private loggingService: LoggingService
) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return next.handle(request); return next.handle(request);

View File

@@ -20,34 +20,24 @@ interface AccountDetails {
products: string[]; products: string[];
category?: string; category?: string;
vcard: { vcard: {
email: [ email: [{
{
value: string; value: string;
} }];
]; fn: [{
fn: [
{
value: string; value: string;
} }];
]; n: [{
n: [
{
value: string[]; value: string[];
} }];
]; tel: [{
tel: [
{
meta: { meta: {
TYP: string[]; TYP: string[];
}; },
value: string; value: string;
} }],
]; version: [{
version: [
{
value: string; value: string;
} }];
];
}; };
} }
@@ -85,35 +75,31 @@ const defaultAccount: AccountDetails = {
}, },
products: [], products: [],
vcard: { vcard: {
email: [ email: [{
{
value: '', value: '',
}, }],
], fn: [{
fn: [
{
value: 'Sarafu Contract', value: 'Sarafu Contract',
}, }],
], n: [{
n: [
{
value: ['Sarafu', 'Contract'], value: ['Sarafu', 'Contract'],
}, }],
], tel: [{
tel: [
{
meta: { meta: {
TYP: [], TYP: [],
}, },
value: '', value: '',
}, }],
], version: [{
version: [
{
value: '3.0', value: '3.0',
}, }],
],
}, },
}; };
export { AccountDetails, Signature, Meta, MetaResponse, defaultAccount }; export {
AccountDetails,
Signature,
Meta,
MetaResponse,
defaultAccount
};

View File

@@ -21,4 +21,9 @@ interface AreaType {
area: Array<string>; area: Array<string>;
} }
export { Action, Category, AreaName, AreaType }; export {
Action,
Category,
AreaName,
AreaType
};

View File

@@ -17,4 +17,7 @@ class W3 {
provider: any; provider: any;
} }
export { Settings, W3 }; export {
Settings,
W3
};

View File

@@ -6,4 +6,6 @@ interface Staff {
userid: string; userid: string;
} }
export { Staff }; export {
Staff
};

View File

@@ -8,10 +8,12 @@ interface Token {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: { '0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
weight: string; weight: string;
balance: string; balance: string;
}; }
}; };
reserveRatio?: string; reserveRatio?: string;
owner?: string; owner?: string;
} }
export { Token }; export {
Token
};

View File

@@ -1,4 +1,4 @@
import { AccountDetails } from '@app/_models/account'; import {AccountDetails} from '@app/_models/account';
class BlocksBloom { class BlocksBloom {
low: number; low: number;
@@ -43,4 +43,10 @@ class Conversion {
tx: Tx; tx: Tx;
} }
export { BlocksBloom, TxToken, Tx, Transaction, Conversion }; export {
BlocksBloom,
TxToken,
Tx,
Transaction,
Conversion
};

View File

@@ -31,7 +31,8 @@ interface MutableKeyStore extends KeyStore {
sign(plainText: string): Promise<any>; sign(plainText: string): Promise<any>;
} }
class MutablePgpKeyStore implements MutableKeyStore { class MutablePgpKeyStore implements MutableKeyStore{
async loadKeyring(): Promise<void> { async loadKeyring(): Promise<void> {
await keyring.load(); await keyring.load();
await keyring.store(); await keyring.store();
@@ -76,8 +77,8 @@ class MutablePgpKeyStore implements MutableKeyStore {
async isValidKey(key): Promise<boolean> { async isValidKey(key): Promise<boolean> {
// There is supposed to be an openpgp.readKey() method but I can't find it? // There is supposed to be an openpgp.readKey() method but I can't find it?
const testKey = await openpgp.key.readArmored(key); const _key = await openpgp.key.readArmored(key);
return !testKey.err; return !_key.err;
} }
async isEncryptedPrivateKey(privateKey: any): Promise<boolean> { async isEncryptedPrivateKey(privateKey: any): Promise<boolean> {
@@ -92,12 +93,8 @@ class MutablePgpKeyStore implements MutableKeyStore {
getFingerprint(): string { getFingerprint(): string {
// TODO Handle multiple keys // TODO Handle multiple keys
return ( return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].keyPacket &&
keyring.privateKeys && keyring.privateKeys.keys[0].keyPacket.fingerprint;
keyring.privateKeys.keys[0] &&
keyring.privateKeys.keys[0].keyPacket &&
keyring.privateKeys.keys[0].keyPacket.fingerprint
);
} }
getKeyId(key: any): string { getKeyId(key: any): string {
@@ -106,11 +103,7 @@ class MutablePgpKeyStore implements MutableKeyStore {
getPrivateKeyId(): string { getPrivateKeyId(): string {
// TODO is there a library that comes with angular for doing this? // TODO is there a library that comes with angular for doing this?
return ( return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].getKeyId().toHex();
keyring.privateKeys &&
keyring.privateKeys.keys[0] &&
keyring.privateKeys.keys[0].getKeyId().toHex()
);
} }
getKeysForId(keyId: string): Array<any> { getKeysForId(keyId: string): Array<any> {
@@ -166,4 +159,7 @@ class MutablePgpKeyStore implements MutableKeyStore {
} }
} }
export { MutablePgpKeyStore, MutableKeyStore }; export {
MutablePgpKeyStore,
MutableKeyStore
};

View File

@@ -1,5 +1,5 @@
import { PGPSigner } from '@app/_pgp/pgp-signer'; import { PGPSigner } from '@app/_pgp/pgp-signer';
import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp/pgp-key-store'; import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp/pgp-key-store';
const keystore: MutableKeyStore = new MutablePgpKeyStore(); const keystore: MutableKeyStore = new MutablePgpKeyStore();
describe('PgpSigner', () => { describe('PgpSigner', () => {

View File

@@ -1,5 +1,5 @@
import { MutableKeyStore } from '@app/_pgp/pgp-key-store'; import {MutableKeyStore} from '@app/_pgp/pgp-key-store';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
const openpgp = require('openpgp'); const openpgp = require('openpgp');
@@ -7,12 +7,12 @@ interface Signable {
digest(): string; digest(): string;
} }
interface Signature { type Signature = {
engine: string; engine: string
algo: string; algo: string
data: string; data: string
digest: string; digest: string;
} };
interface Signer { interface Signer {
onsign(signature: Signature): void; onsign(signature: Signature): void;
@@ -24,6 +24,7 @@ interface Signer {
} }
class PGPSigner implements Signer { class PGPSigner implements Signer {
engine = 'pgp'; engine = 'pgp';
algo = 'sha256'; algo = 'sha256';
dgst: string; dgst: string;
@@ -49,9 +50,7 @@ class PGPSigner implements Signer {
} }
public verify(digest: string, signature: Signature): void { public verify(digest: string, signature: Signature): void {
openpgp.signature openpgp.signature.readArmored(signature.data).then((sig) => {
.readArmored(signature.data)
.then((sig) => {
const opts = { const opts = {
message: openpgp.cleartext.fromText(digest), message: openpgp.cleartext.fromText(digest),
publicKeys: this.keyStore.getTrustedKeys(), publicKeys: this.keyStore.getTrustedKeys(),
@@ -66,16 +65,11 @@ class PGPSigner implements Signer {
return; return;
} }
} }
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(`Checked ${i} signature(s) but none valid`, this, {error: '404 Not found!'});
`Checked ${i} signature(s) but none valid`,
this,
{ error: '404 Not found!' }
);
this.onverify(false); this.onverify(false);
}); });
}) }).catch((e) => {
.catch((e) => { this.loggingService.sendErrorLevelMessage(e.message, this, {error: e});
this.loggingService.sendErrorLevelMessage(e.message, this, { error: e });
this.onverify(false); this.onverify(false);
}); });
} }
@@ -92,9 +86,7 @@ class PGPSigner implements Signer {
privateKeys: [pk], privateKeys: [pk],
detached: true, detached: true,
}; };
openpgp openpgp.sign(opts).then((s) => {
.sign(opts)
.then((s) => {
this.signature = { this.signature = {
engine: this.engine, engine: this.engine,
algo: this.algo, algo: this.algo,
@@ -103,12 +95,16 @@ class PGPSigner implements Signer {
digest, digest,
}; };
this.onsign(this.signature); this.onsign(this.signature);
}) }).catch((e) => {
.catch((e) => { this.loggingService.sendErrorLevelMessage(e.message, this, {error: e});
this.loggingService.sendErrorLevelMessage(e.message, this, { error: e });
this.onsign(undefined); this.onsign(undefined);
}); });
} }
} }
export { Signable, Signature, Signer, PGPSigner }; export {
Signable,
Signature,
Signer,
PGPSigner
};

View File

@@ -1,18 +1,19 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js'; import {hobaParseChallengeHeader} from '@src/assets/js/hoba.js';
import { signChallenge } from '@src/assets/js/hoba-pgp.js'; import {signChallenge} from '@src/assets/js/hoba-pgp.js';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp'; import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp';
import { ErrorDialogService } from '@app/_services/error-dialog.service'; import {ErrorDialogService} from '@app/_services/error-dialog.service';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler'; import {HttpError} from '@app/_helpers/global-error-handler';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class AuthService { export class AuthService {
sessionToken: any; sessionToken: any;
sessionLoginCount: number = 0;
mutableKeyStore: MutableKeyStore; mutableKeyStore: MutableKeyStore;
constructor( constructor(
@@ -38,112 +39,106 @@ export class AuthService {
document.getElementById('state').innerHTML = s; document.getElementById('state').innerHTML = s;
} }
getWithToken(): Promise<boolean> { getWithToken(): void {
return new Promise((resolve, reject) => { const xhr: XMLHttpRequest = new XMLHttpRequest();
const headers = { xhr.responseType = 'text';
Authorization: 'Bearer ' + this.sessionToken, xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
'Content-Type': 'application/json;charset=utf-8', xhr.setRequestHeader('Authorization', 'Bearer ' + this.sessionToken);
'x-cic-automerge': 'none', xhr.setRequestHeader('Content-Type', 'application/json');
}; xhr.setRequestHeader('x-cic-automerge', 'none');
const options = { xhr.addEventListener('load', (e) => {
headers, if (xhr.status === 401) {
}; throw new Error('login rejected');
fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) {
return reject(rejectBody(response));
} }
return resolve(true); this.sessionLoginCount++;
}); this.setState('Click button to log in');
return;
}); });
xhr.send();
} }
// TODO rename to send signed challenge and set session. Also separate these responsibilities // TODO rename to send signed challenge and set session. Also separate these responsibilities
sendResponse(hobaResponseEncoded: any): Promise<boolean> { sendResponse(hobaResponseEncoded: any): Promise<boolean> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const headers = { const xhr: XMLHttpRequest = new XMLHttpRequest();
Authorization: 'HOBA ' + hobaResponseEncoded, xhr.responseType = 'text';
'Content-Type': 'application/json;charset=utf-8', xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
'x-cic-automerge': 'none', xhr.setRequestHeader('Authorization', 'HOBA ' + hobaResponseEncoded);
}; xhr.setRequestHeader('Content-Type', 'application/json');
const options = { xhr.setRequestHeader('x-cic-automerge', 'none');
headers, xhr.addEventListener('load', (e) => {
}; if (xhr.status !== 200) {
fetch(environment.cicMetaUrl, options).then((response) => { const error = new HttpError(xhr.statusText, xhr.status);
if (response.status === 401) { return reject(error);
return reject(rejectBody(response));
} }
this.sessionToken = response.headers.get('Token'); this.sessionToken = xhr.getResponseHeader('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken); sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
this.sessionLoginCount++;
this.setState('Click button to log in'); this.setState('Click button to log in');
return resolve(true); return resolve(true);
}); });
xhr.send();
}); });
} }
getChallenge(): Promise<any> { getChallenge(): void {
return new Promise((resolve, reject) => { const xhr: XMLHttpRequest = new XMLHttpRequest();
fetch(environment.cicMetaUrl).then(async (response) => { xhr.responseType = 'arraybuffer';
if (response.status === 401) { xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
const authHeader: string = response.headers.get('WWW-Authenticate'); xhr.onload = async (e) => {
return resolve(hobaParseChallengeHeader(authHeader)); if (xhr.status === 401) {
const authHeader = xhr.getResponseHeader('WWW-Authenticate');
const o = hobaParseChallengeHeader(authHeader);
this.loginResponse(o);
} }
if (!response.ok) { };
return reject(rejectBody(response)); xhr.send();
}
});
});
} }
async login(): Promise<boolean> {
login(): boolean {
if (this.sessionToken !== undefined) { if (this.sessionToken !== undefined) {
try { try {
const response: boolean = await this.getWithToken(); this.getWithToken();
return response === true; return true;
} catch (e) { } catch (e) {
this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e }); this.loggingService.sendErrorLevelMessage('Login token failed', this, {error: e});
} }
} else { } else {
try { try {
const o = await this.getChallenge(); this.getChallenge();
const response: boolean = await this.loginResponse(o);
return response === true;
} catch (e) { } catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e }); this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e});
} }
} }
return false; return false;
} }
async loginResponse(o: { challenge: string; realm: any }): Promise<any> {
async loginResponse(o: { challenge: string, realm: any }): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await signChallenge( const r = await signChallenge(o.challenge,
o.challenge,
o.realm, o.realm,
environment.cicMetaUrl, environment.cicMetaUrl,
this.mutableKeyStore this.mutableKeyStore);
); const sessionTokenResult: boolean = await this.sendResponse(r);
const response: boolean = await this.sendResponse(r);
resolve(response);
} catch (error) { } catch (error) {
if (error instanceof HttpError) { if (error instanceof HttpError) {
if (error.status === 403) { if (error.status === 403) {
this.errorDialogService.openDialog({ message: 'You are not authorized to use this system' });
}
if (error.status === 401) {
this.errorDialogService.openDialog({ this.errorDialogService.openDialog({
message: 'You are not authorized to use this system', message: 'Unable to authenticate with the service. ' +
});
} else if (error.status === 401) {
this.errorDialogService.openDialog({
message:
'Unable to authenticate with the service. ' +
'Please speak with the staff at Grassroots ' + 'Please speak with the staff at Grassroots ' +
'Economics for requesting access ' + 'Economics for requesting access ' +
'staff@grassrootseconomics.net.', 'staff@grassrootseconomics.net.'
}); });
} }
} else {
// TODO define this error
this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
} }
// TODO define this error
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'});
resolve(false); resolve(false);
} }
}); });
@@ -169,11 +164,7 @@ export class AuthService {
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored); const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored); localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
} catch (err) { } catch (err) {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(`Failed to set key: ${err.message || err.statusText}`, this, {error: err});
`Failed to set key: ${err.message || err.statusText}`,
this,
{ error: err }
);
this.errorDialogService.openDialog({ this.errorDialogService.openDialog({
message: `Failed to set key: ${err.message || err.statusText}`, message: `Failed to set key: ${err.message || err.statusText}`,
}); });
@@ -185,26 +176,24 @@ export class AuthService {
logout(): void { logout(): void {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN')); sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
localStorage.removeItem(btoa('CICADA_PRIVATE_KEY'));
this.sessionToken = undefined; this.sessionToken = undefined;
window.location.reload(); window.location.reload(true);
} }
getTrustedUsers(): any { getTrustedUsers(): any {
const trustedUsers: Array<any> = []; const trustedUsers: Array<any> = [];
this.mutableKeyStore.getPublicKeys().forEach((key) => trustedUsers.push(key.users[0].userId)); this.mutableKeyStore.getPublicKeys().forEach(key => trustedUsers.push(key.users[0].userId));
return trustedUsers; return trustedUsers;
} }
async getPublicKeys(): Promise<any> { async getPublicKeys(): Promise<any> {
return new Promise((resolve, reject) => { return await fetch(environment.publicKeysUrl)
fetch(environment.publicKeysUrl).then((res) => { .then(res => {
if (!res.ok) { if (!res.ok) {
// TODO does angular recommend an error interface? // TODO does angular recommend an error interface?
return reject(rejectBody(res)); throw Error(`${res.statusText} - ${res.status}`);
} }
return resolve(res.text()); return res.text();
});
}); });
} }

View File

@@ -1,15 +1,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { BlockSyncService } from '@app/_services/block-sync.service'; import { BlockSyncService } from '@app/_services/block-sync.service';
import { TransactionService } from '@app/_services/transaction.service'; import {TransactionService} from '@app/_services/transaction.service';
import { TransactionServiceStub } from '@src/testing'; import {TransactionServiceStub} from '@src/testing';
describe('BlockSyncService', () => { describe('BlockSyncService', () => {
let service: BlockSyncService; let service: BlockSyncService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [{ provide: TransactionService, useClass: TransactionServiceStub }], providers: [
{ provide: TransactionService, useClass: TransactionServiceStub }
]
}); });
service = TestBed.inject(BlockSyncService); service = TestBed.inject(BlockSyncService);
}); });

View File

@@ -1,14 +1,14 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { Settings } from '@app/_models'; import {Settings} from '@app/_models';
import { TransactionHelper } from 'cic-client'; import {TransactionHelper} from 'cic-client';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { TransactionService } from '@app/_services/transaction.service'; import {TransactionService} from '@app/_services/transaction.service';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { RegistryService } from '@app/_services/registry.service'; import {RegistryService} from '@app/_services/registry.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class BlockSyncService { export class BlockSyncService {
readyStateTarget: number = 2; readyStateTarget: number = 2;
@@ -17,8 +17,8 @@ export class BlockSyncService {
constructor( constructor(
private transactionService: TransactionService, private transactionService: TransactionService,
private loggingService: LoggingService, private loggingService: LoggingService,
private registryService: RegistryService private registryService: RegistryService,
) {} ) { }
blockSync(address: string = null, offset: number = 0, limit: number = 100): void { blockSync(address: string = null, offset: number = 0, limit: number = 100): void {
this.transactionService.resetTransactionsList(); this.transactionService.resetTransactionsList();
@@ -43,14 +43,7 @@ export class BlockSyncService {
settings.registry.load(); settings.registry.load();
} }
readyStateProcessor( readyStateProcessor(settings: Settings, bit: number, address: string, offset: number, limit: number): void {
settings: Settings,
bit: number,
address: string,
offset: number,
limit: number
): void {
// tslint:disable-next-line:no-bitwise
this.readyState |= bit; this.readyState |= bit;
if (this.readyStateTarget === this.readyState && this.readyStateTarget) { if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js'); const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js');
@@ -61,17 +54,11 @@ export class BlockSyncService {
w3_provider: settings.w3.provider, w3_provider: settings.w3.provider,
}); });
if (address === null) { if (address === null) {
this.transactionService this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
.getAllTransactions(offset, limit)
.pipe(first())
.subscribe((res) => {
this.fetcher(settings, res); this.fetcher(settings, res);
}); });
} else { } else {
this.transactionService this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => {
.getAddressTransactions(address, offset, limit)
.pipe(first())
.subscribe((res) => {
this.fetcher(settings, res); this.fetcher(settings, res);
}); });
} }
@@ -94,14 +81,7 @@ export class BlockSyncService {
}); });
} }
async scan( async scan(settings: Settings, lo: number, hi: number, bloomBlockBytes: Uint8Array, bloomBlocktxBytes: Uint8Array, bloomRounds: any): Promise<void> {
settings: Settings,
lo: number,
hi: number,
bloomBlockBytes: Uint8Array,
bloomBlocktxBytes: Uint8Array,
bloomRounds: any
): Promise<void> {
const w: Worker = new Worker('./../assets/js/block-sync/ondemand.js'); const w: Worker = new Worker('./../assets/js/block-sync/ondemand.js');
w.onmessage = (m) => { w.onmessage = (m) => {
settings.txHelper.processReceipt(m.data); settings.txHelper.processReceipt(m.data);
@@ -110,7 +90,10 @@ export class BlockSyncService {
w3_provider: settings.w3.provider, w3_provider: settings.w3.provider,
lo, lo,
hi, hi,
filters: [bloomBlockBytes, bloomBlocktxBytes], filters: [
bloomBlockBytes,
bloomBlocktxBytes,
],
filter_rounds: bloomRounds, filter_rounds: bloomRounds,
}); });
} }
@@ -118,19 +101,12 @@ export class BlockSyncService {
fetcher(settings: Settings, transactionsInfo: any): void { fetcher(settings: Settings, transactionsInfo: any): void {
const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter); const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter);
const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length); const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length);
bOne.map((e, i, v) => (v[i] = blockFilterBinstr.charCodeAt(i))); bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i));
const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter); const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter);
const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length); const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length);
bTwo.map((e, i, v) => (v[i] = blocktxFilterBinstr.charCodeAt(i))); bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i));
settings.scanFilter( settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds);
settings,
transactionsInfo.low,
transactionsInfo.high,
bOne,
bTwo,
transactionsInfo.filter_rounds
);
} }
} }

View File

@@ -1,14 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import {MatDialog, MatDialogRef} from '@angular/material/dialog';
import { ErrorDialogComponent } from '@app/shared/error-dialog/error-dialog.component'; import {ErrorDialogComponent} from '@app/shared/error-dialog/error-dialog.component';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class ErrorDialogService { export class ErrorDialogService {
public isDialogOpen: boolean = false; public isDialogOpen: boolean = false;
constructor(public dialog: MatDialog) {} constructor(
public dialog: MatDialog,
) { }
openDialog(data): any { openDialog(data): any {
if (this.isDialogOpen) { if (this.isDialogOpen) {
@@ -17,9 +19,9 @@ export class ErrorDialogService {
this.isDialogOpen = true; this.isDialogOpen = true;
const dialogRef: MatDialogRef<any> = this.dialog.open(ErrorDialogComponent, { const dialogRef: MatDialogRef<any> = this.dialog.open(ErrorDialogComponent, {
width: '300px', width: '300px',
data, data
}); });
dialogRef.afterClosed().subscribe(() => (this.isDialogOpen = false)); dialogRef.afterClosed().subscribe(() => this.isDialogOpen = false);
} }
} }

View File

@@ -1,14 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import {Observable} from 'rxjs';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class LocationService { export class LocationService {
constructor(private httpClient: HttpClient) {}
constructor(
private httpClient: HttpClient,
) { }
getAreaNames(): Observable<any> { getAreaNames(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/areanames`); return this.httpClient.get(`${environment.cicMetaUrl}/areanames`);
@@ -23,8 +26,6 @@ export class LocationService {
} }
getAreaTypeByArea(area: string): Observable<any> { getAreaTypeByArea(area: string): Observable<any> {
return this.httpClient return this.httpClient.get(`${environment.cicMetaUrl}/areatypes/${area.toLowerCase()}`).pipe(first());
.get(`${environment.cicMetaUrl}/areatypes/${area.toLowerCase()}`)
.pipe(first());
} }
} }

View File

@@ -1,8 +1,8 @@
import { Injectable, isDevMode } from '@angular/core'; import {Injectable, isDevMode} from '@angular/core';
import { NGXLogger } from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class LoggingService { export class LoggingService {
env: string; env: string;

View File

@@ -1,28 +1,27 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import Web3 from 'web3'; import Web3 from 'web3';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { CICRegistry, FileGetter } from 'cic-client'; import {CICRegistry, FileGetter} from 'cic-client';
import { HttpGetter } from '@app/_helpers'; import {HttpGetter} from '@app/_helpers';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class RegistryService { export class RegistryService {
web3: Web3 = new Web3(environment.web3Provider); web3: Web3 = new Web3(environment.web3Provider);
fileGetter: FileGetter = new HttpGetter(); fileGetter: FileGetter = new HttpGetter();
registry: CICRegistry = new CICRegistry( registry: CICRegistry = new CICRegistry(this.web3, environment.registryAddress, 'CICRegistry', this.fileGetter,
this.web3, ['../../assets/js/block-sync/data']);
environment.registryAddress,
'Registry',
this.fileGetter,
['../../assets/js/block-sync/data']
);
constructor() { constructor() {
this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress); this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress);
this.registry.load(); this.registry.load();
} }
async load(): Promise<any> {
this.registry.load();
}
getRegistry(): any { getRegistry(): any {
return this.registry; return this.registry;
} }

View File

@@ -1,33 +1,34 @@
import { EventEmitter, Injectable } from '@angular/core'; import { EventEmitter, Injectable } from '@angular/core';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { BehaviorSubject, Observable } from 'rxjs'; import {BehaviorSubject, Observable} from 'rxjs';
import { CICRegistry } from 'cic-client'; import {CICRegistry} from 'cic-client';
import { TokenRegistry } from '@app/_eth'; import {TokenRegistry} from '@app/_eth';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { RegistryService } from '@app/_services/registry.service'; import {RegistryService} from '@app/_services/registry.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class TokenService { export class TokenService {
registry: CICRegistry; registry: CICRegistry;
tokenRegistry: TokenRegistry; tokenRegistry: TokenRegistry;
LoadEvent: EventEmitter<number> = new EventEmitter<number>(); LoadEvent: EventEmitter<number> = new EventEmitter<number>();
constructor(private httpClient: HttpClient, private registryService: RegistryService) { constructor(
private httpClient: HttpClient,
private registryService: RegistryService,
) {
this.registry = registryService.getRegistry(); this.registry = registryService.getRegistry();
this.registry.load(); this.registry.load();
this.registry.onload = async (address: string): Promise<void> => { this.registry.onload = async (address: string): Promise<void> => {
this.tokenRegistry = new TokenRegistry( this.tokenRegistry = new TokenRegistry(await this.registry.getContractAddressByName('TokenRegistry'));
await this.registry.getContractAddressByName('TokenRegistry')
);
this.LoadEvent.next(Date.now()); this.LoadEvent.next(Date.now());
}; };
} }
async getTokens(): Promise<Array<Promise<string>>> { async getTokens(): Promise<Array<Promise<string>>> {
const count: number = await this.tokenRegistry.totalTokens(); const count: number = await this.tokenRegistry.totalTokens();
return Array.from({ length: count }, async (v, i) => await this.tokenRegistry.entry(i)); return Array.from({length: count}, async (v, i) => await this.tokenRegistry.entry(i));
} }
getTokenBySymbol(symbol: string): Observable<any> { getTokenBySymbol(symbol: string): Observable<any> {

View File

@@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { TransactionService } from '@app/_services/transaction.service'; import { TransactionService } from '@app/_services/transaction.service';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
describe('TransactionService', () => { describe('TransactionService', () => {
let httpClient: HttpClient; let httpClient: HttpClient;
@@ -11,7 +11,7 @@ describe('TransactionService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [HttpClientTestingModule]
}); });
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);

View File

@@ -1,26 +1,26 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { BehaviorSubject, Observable } from 'rxjs'; import {BehaviorSubject, Observable} from 'rxjs';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { Envelope, User } from 'cic-client-meta'; import {Envelope, User} from 'cic-client-meta';
import { UserService } from '@app/_services/user.service'; import {UserService} from '@app/_services/user.service';
import { Keccak } from 'sha3'; import { Keccak } from 'sha3';
import { utils } from 'ethers'; import { utils } from 'ethers';
import { add0x, fromHex, strip0x, toHex } from '@src/assets/js/ethtx/dist/hex'; import {add0x, fromHex, strip0x, toHex} from '@src/assets/js/ethtx/dist/hex';
import { Tx } from '@src/assets/js/ethtx/dist'; import {Tx} from '@src/assets/js/ethtx/dist';
import { toValue } from '@src/assets/js/ethtx/dist/tx'; import {toValue} from '@src/assets/js/ethtx/dist/tx';
import * as secp256k1 from 'secp256k1'; import * as secp256k1 from 'secp256k1';
import { AuthService } from '@app/_services/auth.service'; import {AuthService} from '@app/_services/auth.service';
import { defaultAccount } from '@app/_models'; import {defaultAccount} from '@app/_models';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { CICRegistry } from 'cic-client'; import {CICRegistry} from 'cic-client';
import { RegistryService } from '@app/_services/registry.service'; import {RegistryService} from '@app/_services/registry.service';
import Web3 from 'web3'; import Web3 from 'web3';
const vCard = require('vcard-parser'); const vCard = require('vcard-parser');
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class TransactionService { export class TransactionService {
transactions: any[] = []; transactions: any[] = [];
@@ -35,7 +35,7 @@ export class TransactionService {
private authService: AuthService, private authService: AuthService,
private userService: UserService, private userService: UserService,
private loggingService: LoggingService, private loggingService: LoggingService,
private registryService: RegistryService private registryService: RegistryService,
) { ) {
this.web3 = this.registryService.getWeb3(); this.web3 = this.registryService.getWeb3();
this.registry = registryService.getRegistry(); this.registry = registryService.getRegistry();
@@ -51,58 +51,36 @@ export class TransactionService {
} }
async setTransaction(transaction, cacheSize: number): Promise<void> { async setTransaction(transaction, cacheSize: number): Promise<void> {
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === transaction.tx.txHash)) { if (this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash)) { return; }
return;
}
transaction.value = Number(transaction.value); transaction.value = Number(transaction.value);
transaction.type = 'transaction'; transaction.type = 'transaction';
try { try {
this.userService this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.from)).pipe(first()).subscribe((res) => {
.getAccountDetailsFromMeta(await User.toKey(transaction.from))
.pipe(first())
.subscribe(
(res) => {
transaction.sender = this.getAccountInfo(res.body); transaction.sender = this.getAccountInfo(res.body);
}, }, error => {
(error) => {
transaction.sender = defaultAccount; transaction.sender = defaultAccount;
} });
); this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe((res) => {
this.userService
.getAccountDetailsFromMeta(await User.toKey(transaction.to))
.pipe(first())
.subscribe(
(res) => {
transaction.recipient = this.getAccountInfo(res.body); transaction.recipient = this.getAccountInfo(res.body);
}, }, error => {
(error) => {
transaction.recipient = defaultAccount; transaction.recipient = defaultAccount;
} });
);
} finally { } finally {
this.addTransaction(transaction, cacheSize); this.addTransaction(transaction, cacheSize);
} }
} }
async setConversion(conversion, cacheSize): Promise<void> { async setConversion(conversion, cacheSize): Promise<void> {
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === conversion.tx.txHash)) { if (this.transactions.find(cachedTx => cachedTx.tx.txHash === conversion.tx.txHash)) { return; }
return;
}
conversion.type = 'conversion'; conversion.type = 'conversion';
conversion.fromValue = Number(conversion.fromValue); conversion.fromValue = Number(conversion.fromValue);
conversion.toValue = Number(conversion.toValue); conversion.toValue = Number(conversion.toValue);
try { try {
this.userService this.userService.getAccountDetailsFromMeta(await User.toKey(conversion.trader)).pipe(first()).subscribe((res) => {
.getAccountDetailsFromMeta(await User.toKey(conversion.trader))
.pipe(first())
.subscribe(
(res) => {
conversion.sender = conversion.recipient = this.getAccountInfo(res.body); conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
}, }, error => {
(error) => {
conversion.sender = conversion.recipient = defaultAccount; conversion.sender = conversion.recipient = defaultAccount;
} });
);
} finally { } finally {
this.addTransaction(conversion, cacheSize); this.addTransaction(conversion, cacheSize);
} }
@@ -122,29 +100,19 @@ export class TransactionService {
} }
getAccountInfo(account: string): any { getAccountInfo(account: string): any {
const accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data; let accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
return accountInfo; return accountInfo;
} }
async transferRequest( async transferRequest(tokenAddress: string, senderAddress: string, recipientAddress: string, value: number): Promise<any> {
tokenAddress: string, const transferAuthAddress = await this.registry.getContractAddressByName('TransferAuthorization');
senderAddress: string,
recipientAddress: string,
value: number
): Promise<any> {
const transferAuthAddress = await this.registry.getContractAddressByName(
'TransferAuthorization'
);
const hashFunction = new Keccak(256); const hashFunction = new Keccak(256);
hashFunction.update('createRequest(address,address,address,uint256)'); hashFunction.update('createRequest(address,address,address,uint256)');
const hash = hashFunction.digest(); const hash = hashFunction.digest();
const methodSignature = hash.toString('hex').substring(0, 8); const methodSignature = hash.toString('hex').substring(0, 8);
const abiCoder = new utils.AbiCoder(); const abiCoder = new utils.AbiCoder();
const abi = await abiCoder.encode( const abi = await abiCoder.encode(['address', 'address', 'address', 'uint256'], [senderAddress, recipientAddress, tokenAddress, value]);
['address', 'address', 'address', 'uint256'],
[senderAddress, recipientAddress, tokenAddress, value]
);
const data = fromHex(methodSignature + strip0x(abi)); const data = fromHex(methodSignature + strip0x(abi));
const tx = new Tx(environment.bloxbergChainId); const tx = new Tx(environment.bloxbergChainId);
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress); tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);

View File

@@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { UserService } from '@app/_services/user.service'; import { UserService } from '@app/_services/user.service';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
describe('UserService', () => { describe('UserService', () => {
let httpClient: HttpClient; let httpClient: HttpClient;
@@ -11,7 +11,7 @@ describe('UserService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule], imports: [HttpClientTestingModule]
}); });
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
@@ -34,7 +34,7 @@ describe('UserService', () => {
failedPinAttempts: 1, failedPinAttempts: 1,
status: 'approved', status: 'approved',
bio: 'Bodaboda', bio: 'Bodaboda',
gender: 'male', gender: 'male'
}); });
}); });
@@ -48,7 +48,7 @@ describe('UserService', () => {
user: 'Tom', user: 'Tom',
role: 'enroller', role: 'enroller',
action: 'Disburse RSV 100', action: 'Disburse RSV 100',
approval: false, approval: false
}); });
}); });
@@ -63,7 +63,7 @@ describe('UserService', () => {
user: 'Tom', user: 'Tom',
role: 'enroller', role: 'enroller',
action: 'Disburse RSV 100', action: 'Disburse RSV 100',
approval: true, approval: true
}); });
}); });
@@ -74,7 +74,7 @@ describe('UserService', () => {
user: 'Christine', user: 'Christine',
role: 'admin', role: 'admin',
action: 'Change user phone number', action: 'Change user phone number',
approval: false, approval: false
}); });
}); });
}); });

View File

@@ -1,34 +1,32 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import {BehaviorSubject, Observable, Subject, throwError, of} from 'rxjs';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { ArgPair, Envelope, Phone, Syncable, User } from 'cic-client-meta'; import {ArgPair, Envelope, Phone, Syncable, User} from 'cic-client-meta';
import { AccountDetails } from '@app/_models'; import {AccountDetails} from '@app/_models';
import { LoggingService } from '@app/_services/logging.service'; import {LoggingService} from '@app/_services/logging.service';
import { TokenService } from '@app/_services/token.service'; import {TokenService} from '@app/_services/token.service';
import { AccountIndex } from '@app/_eth'; import {AccountIndex} from '@app/_eth';
import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp'; import {MutableKeyStore, PGPSigner, Signer} from '@app/_pgp';
import { RegistryService } from '@app/_services/registry.service'; import {RegistryService} from '@app/_services/registry.service';
import { CICRegistry } from 'cic-client'; import {CICRegistry} from 'cic-client';
import { AuthService } from '@app/_services/auth.service'; import {AuthService} from '@app/_services/auth.service';
import { personValidation, vcardValidation } from '@app/_helpers'; import {personValidation, vcardValidation} from '@app/_helpers';
import { add0x } from '@src/assets/js/ethtx/dist/hex'; import {add0x} from '@src/assets/js/ethtx/dist/hex';
const vCard = require('vcard-parser'); const vCard = require('vcard-parser');
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root'
}) })
export class UserService { export class UserService {
headers: HttpHeaders = new HttpHeaders({ 'x-cic-automerge': 'client' }); headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'});
keystore: MutableKeyStore; keystore: MutableKeyStore;
signer: Signer; signer: Signer;
registry: CICRegistry; registry: CICRegistry;
accounts: Array<AccountDetails> = []; accounts: Array<AccountDetails> = [];
private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject< private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<Array<AccountDetails>>(this.accounts);
Array<AccountDetails>
>(this.accounts);
accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable(); accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable();
actions: Array<any> = []; actions: Array<any> = [];
@@ -42,48 +40,56 @@ export class UserService {
private registryService: RegistryService, private registryService: RegistryService,
private authService: AuthService private authService: AuthService
) { ) {
this.authService.init().then(() => { //this.authService.init().then(() => {
this.keystore = authService.mutableKeyStore; // this.keystore = authService.mutableKeyStore;
// this.signer = new PGPSigner(this.keystore);
//});
//this.registry = registryService.getRegistry();
//this.registry.load();
}
async load(): Promise<any> {
try {
// TODO this method is called by ngOnInit so we need to
// emit an observalbe or conver ngonInit to promise
// TODO alig the load/init methods naming
await this.authService.init();
// await this.registryService.load();
// TODO key store is defined
this.keystore = this.authService.mutableKeyStore;
this.signer = new PGPSigner(this.keystore); this.signer = new PGPSigner(this.keystore);
}); this.registry = this.registryService.getRegistry();
this.registry = registryService.getRegistry(); return of(0);
this.registry.load(); } catch (error) {
console.log('ERROR: Failed to initiialize User Service', error)
return throwError(error);
}
} }
resetPin(phone: string): Observable<any> { resetPin(phone: string): Observable<any> {
const params: HttpParams = new HttpParams().set('phoneNumber', phone); const params: HttpParams = new HttpParams().set('phoneNumber', phone);
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params }); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
} }
getAccountStatus(phone: string): Observable<any> { getAccountStatus(phone: string): Observable<any> {
const params: HttpParams = new HttpParams().set('phoneNumber', phone); const params: HttpParams = new HttpParams().set('phoneNumber', phone);
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params }); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
} }
getLockedAccounts(offset: number, limit: number): Observable<any> { getLockedAccounts(offset: number, limit: number): Observable<any> {
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`); return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
} }
async changeAccountInfo( async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string,
address: string, businessCategory: string, userLocation: string, location: string, locationType: string
name: string,
phoneNumber: string,
age: string,
type: string,
bio: string,
gender: string,
businessCategory: string,
userLocation: string,
location: string,
locationType: string
): Promise<any> { ): Promise<any> {
const accountInfo: any = { const accountInfo: any = {
vcard: { vcard: {
fn: [{}], fn: [{}],
n: [{}], n: [{}],
tel: [{}], tel: [{}]
}, },
location: {}, location: {}
}; };
accountInfo.vcard.fn[0].value = name; accountInfo.vcard.fn[0].value = name;
accountInfo.vcard.n[0].value = name.split(' '); accountInfo.vcard.n[0].value = name.split(' ');
@@ -99,52 +105,33 @@ export class UserService {
await vcardValidation(accountInfo.vcard); await vcardValidation(accountInfo.vcard);
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard)); accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
const accountKey: string = await User.toKey(address); const accountKey: string = await User.toKey(address);
this.getAccountDetailsFromMeta(accountKey) this.getAccountDetailsFromMeta(accountKey).pipe(first()).subscribe(async res => {
.pipe(first())
.subscribe(
async (res) => {
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const update: Array<ArgPair> = []; const update: Array<ArgPair> = [];
for (const prop of Object.keys(accountInfo)) { for (const prop in accountInfo) {
update.push(new ArgPair(prop, accountInfo[prop])); update.push(new ArgPair(prop, accountInfo[prop]));
} }
syncableAccount.update(update, 'client-branch'); syncableAccount.update(update, 'client-branch');
await personValidation(syncableAccount.m.data); await personValidation(syncableAccount.m.data);
await this.updateMeta(syncableAccount, accountKey, this.headers); await this.updateMeta(syncableAccount, accountKey, this.headers);
}, }, async error => {
async (error) => { this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error});
this.loggingService.sendErrorLevelMessage(
'Cannot find account info in meta service',
this,
{ error }
);
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo); const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
await this.updateMeta(syncableAccount, accountKey, this.headers); await this.updateMeta(syncableAccount, accountKey, this.headers);
} });
);
return accountKey; return accountKey;
} }
async updateMeta( async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> {
syncableAccount: Syncable, const envelope: Envelope = await this.wrap(syncableAccount , this.signer);
accountKey: string,
headers: HttpHeaders
): Promise<any> {
const envelope: Envelope = await this.wrap(syncableAccount, this.signer);
const reqBody: string = envelope.toJSON(); const reqBody: string = envelope.toJSON();
this.httpClient this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
.pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
}); });
} }
getActions(): void { getActions(): void {
this.httpClient this.httpClient.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res));
.get(`${environment.cicCacheUrl}/actions`)
.pipe(first())
.subscribe((res) => this.actionsList.next(res));
} }
getActionById(id: string): Observable<any> { getActionById(id: string): Observable<any> {
@@ -179,33 +166,29 @@ export class UserService {
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> { async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
this.resetAccountsList(); this.resetAccountsList();
const accountIndexAddress: string = await this.registry.getContractAddressByName( let accountAddresses: Array<string>;
'AccountRegistry' try {
); const accountIndexAddress: string = await this.registry.getContractAddressByName('AccountRegistry');
const accountIndexQuery = new AccountIndex(accountIndexAddress); const accountIndexQuery = new AccountIndex(accountIndexAddress);
const accountAddresses: Array<string> = await accountIndexQuery.last( const totalAccounts = await accountIndexQuery.totalAccounts()
await accountIndexQuery.totalAccounts() const accountAddresses = await accountIndexQuery.last(totalAccounts);
);
this.loggingService.sendInfoLevelMessage(accountAddresses); this.loggingService.sendInfoLevelMessage(accountAddresses);
} catch (error){
// TODO real logging:
console.log("ERROR: failed to load accounts \n", error)
}
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) { for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
await this.getAccountByAddress(accountAddress, limit); await this.getAccountByAddress(accountAddress, limit);
} }
} }
async getAccountByAddress( async getAccountByAddress(accountAddress: string, limit: number = 100): Promise<Observable<AccountDetails>> {
accountAddress: string, let accountSubject: Subject<any> = new Subject<any>();
limit: number = 100 this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress))).pipe(first()).subscribe(async res => {
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
.pipe(first())
.subscribe(async (res) => {
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const accountInfo = account.m.data; const accountInfo = account.m.data;
await personValidation(accountInfo); await personValidation(accountInfo);
accountInfo.balance = await this.tokenService.getTokenBalance( accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]);
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
);
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
await vcardValidation(accountInfo.vcard); await vcardValidation(accountInfo.vcard);
this.accounts.unshift(accountInfo); this.accounts.unshift(accountInfo);
@@ -218,18 +201,13 @@ export class UserService {
return accountSubject.asObservable(); return accountSubject.asObservable();
} }
async getAccountByPhone( async getAccountByPhone(phoneNumber: string, limit: number = 100): Promise<Observable<AccountDetails>> {
phoneNumber: string, let accountSubject: Subject<any> = new Subject<any>();
limit: number = 100 this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber)).pipe(first()).subscribe(async res => {
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber))
.pipe(first())
.subscribe(async (res) => {
const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const address: string = response.m.data; const address: string = response.m.data;
const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit); const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit);
account.subscribe((result) => { account.subscribe(result => {
accountSubject.next(result); accountSubject.next(result);
}); });
}); });
@@ -241,9 +219,7 @@ export class UserService {
this.accountsList.next(this.accounts); this.accountsList.next(this.accounts);
} }
searchAccountByName(name: string): any { searchAccountByName(name: string): any { return; }
return;
}
getCategories(): Observable<any> { getCategories(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/categories`); return this.httpClient.get(`${environment.cicMetaUrl}/categories`);
@@ -265,3 +241,5 @@ export class UserService {
return this.httpClient.get(`${environment.cicMetaUrl}/genders`); return this.httpClient.get(`${environment.cicMetaUrl}/genders`);
} }
} }

View File

@@ -1,24 +1,17 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router'; import {Routes, RouterModule, PreloadAllModules} from '@angular/router';
import { AuthGuard } from '@app/_guards'; import {AuthGuard} from '@app/_guards';
const routes: Routes = [ const routes: Routes = [
{ path: 'auth', loadChildren: () => import('@app/auth/auth.module').then((m) => m.AuthModule) }, { path: 'auth', loadChildren: () => import('@app/auth/auth.module').then(m => m.AuthModule) },
{ { path: '', loadChildren: () => import('@pages/pages.module').then(m => m.PagesModule), canActivate: [AuthGuard] },
path: '', { path: '**', redirectTo: '', pathMatch: 'full' }
loadChildren: () => import('@pages/pages.module').then((m) => m.PagesModule),
canActivate: [AuthGuard],
},
{ path: '**', redirectTo: '', pathMatch: 'full' },
]; ];
@NgModule({ @NgModule({
imports: [ imports: [RouterModule.forRoot(routes, {
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules
preloadingStrategy: PreloadAllModules, })],
useHash: true, exports: [RouterModule]
}),
],
exports: [RouterModule],
}) })
export class AppRoutingModule {} export class AppRoutingModule { }

View File

@@ -1,2 +1 @@
<app-network-status></app-network-status>
<router-outlet (activate)="onResize(mediaQuery)"></router-outlet> <router-outlet (activate)="onResize(mediaQuery)"></router-outlet>

View File

@@ -1,20 +1,24 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from '@app/app.component'; import { AppComponent } from '@app/app.component';
import { TransactionService } from '@app/_services'; import {TransactionService} from '@app/_services';
import { import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, TransactionServiceStub} from '@src/testing';
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
TransactionServiceStub,
} from '@src/testing';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [RouterTestingModule], imports: [
declarations: [AppComponent, FooterStubComponent, SidebarStubComponent, TopbarStubComponent], RouterTestingModule
providers: [{ provide: TransactionService, useClass: TransactionServiceStub }], ],
declarations: [
AppComponent,
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent
],
providers: [
{ provide: TransactionService, useClass: TransactionServiceStub }
]
}).compileComponents(); }).compileComponents();
}); });

View File

@@ -1,20 +1,14 @@
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, HostListener} from '@angular/core';
import { import {AuthService, ErrorDialogService, LoggingService, TransactionService} from '@app/_services';
AuthService, import {catchError} from 'rxjs/operators';
ErrorDialogService,
LoggingService,
TransactionService,
} from '@app/_services';
import { catchError } from 'rxjs/operators';
import { SwUpdate } from '@angular/service-worker';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'], styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AppComponent implements OnInit { export class AppComponent {
title = 'CICADA'; title = 'CICADA';
readyStateTarget: number = 3; readyStateTarget: number = 3;
readyState: number = 0; readyState: number = 0;
@@ -24,8 +18,7 @@ export class AppComponent implements OnInit {
private authService: AuthService, private authService: AuthService,
private transactionService: TransactionService, private transactionService: TransactionService,
private loggingService: LoggingService, private loggingService: LoggingService,
private errorDialogService: ErrorDialogService, private errorDialogService: ErrorDialogService
private swUpdate: SwUpdate
) { ) {
(async () => { (async () => {
try { try {
@@ -38,26 +31,14 @@ export class AppComponent implements OnInit {
const publicKeys = await this.authService.getPublicKeys(); const publicKeys = await this.authService.getPublicKeys();
await this.authService.mutableKeyStore.importPublicKey(publicKeys); await this.authService.mutableKeyStore.importPublicKey(publicKeys);
} catch (error) { } catch (error) {
this.errorDialogService.openDialog({ this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'});
message: 'Trusted keys endpoint cannot be reached. Please try again later.',
});
// TODO do something to halt user progress...show a sad cicada page 🦗? // TODO do something to halt user progress...show a sad cicada page 🦗?
} }
})(); })();
this.mediaQuery.addEventListener('change', this.onResize); this.mediaQuery.addListener(this.onResize);
this.onResize(this.mediaQuery); this.onResize(this.mediaQuery);
} }
ngOnInit(): void {
if (!this.swUpdate.isEnabled) {
this.swUpdate.available.subscribe(() => {
if (confirm('New Version available. Load New Version?')) {
window.location.reload();
}
});
}
}
// Load resize // Load resize
onResize(e): void { onResize(e): void {
const sidebar: HTMLElement = document.getElementById('sidebar'); const sidebar: HTMLElement = document.getElementById('sidebar');

View File

@@ -1,36 +1,41 @@
import { BrowserModule } from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core'; import {ErrorHandler, NgModule} from '@angular/core';
import { AppRoutingModule } from '@app/app-routing.module'; import {AppRoutingModule} from '@app/app-routing.module';
import { AppComponent } from '@app/app.component'; import {AppComponent} from '@app/app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import { GlobalErrorHandler, MockBackendProvider } from '@app/_helpers'; import {
import { SharedModule } from '@app/shared/shared.module'; GlobalErrorHandler,
import { MatTableModule } from '@angular/material/table'; MockBackendProvider,
import { AuthGuard } from '@app/_guards'; } from '@app/_helpers';
import { LoggerModule } from 'ngx-logger'; import {DataTablesModule} from 'angular-datatables';
import { environment } from '@src/environments/environment'; import {SharedModule} from '@app/shared/shared.module';
import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors'; import {MatTableModule} from '@angular/material/table';
import { MutablePgpKeyStore } from '@app/_pgp'; import {AuthGuard} from '@app/_guards';
import { ServiceWorkerModule } from '@angular/service-worker'; import {LoggerModule} from 'ngx-logger';
import {environment} from '@src/environments/environment';
import {ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor} from '@app/_interceptors';
import {MutablePgpKeyStore} from '@app/_pgp';
@NgModule({ @NgModule({
declarations: [AppComponent], declarations: [
AppComponent
],
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
HttpClientModule, HttpClientModule,
DataTablesModule,
SharedModule, SharedModule,
MatTableModule, MatTableModule,
LoggerModule.forRoot({ LoggerModule.forRoot({
level: environment.logLevel, level: environment.logLevel,
serverLogLevel: environment.serverLogLevel, serverLogLevel: environment.serverLogLevel,
serverLoggingUrl: `${environment.loggingUrl}/api/logs/`, serverLoggingUrl: `${environment.loggingUrl}/api/logs/`,
disableConsoleLogging: false, disableConsoleLogging: false
}), })
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
], ],
providers: [ providers: [
AuthGuard, AuthGuard,
@@ -42,6 +47,6 @@ import { ServiceWorkerModule } from '@angular/service-worker';
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
], ],
bootstrap: [AppComponent], bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule { }

View File

@@ -1,9 +1,7 @@
import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive'; import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive';
import { ElementRef, Renderer2 } from '@angular/core'; import {ElementRef, Renderer2} from '@angular/core';
// tslint:disable-next-line:prefer-const
let elementRef: ElementRef; let elementRef: ElementRef;
// tslint:disable-next-line:prefer-const
let renderer: Renderer2; let renderer: Renderer2;
describe('PasswordToggleDirective', () => { describe('PasswordToggleDirective', () => {

View File

@@ -1,7 +1,7 @@
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core'; import {Directive, ElementRef, Input, Renderer2} from '@angular/core';
@Directive({ @Directive({
selector: '[appPasswordToggle]', selector: '[appPasswordToggle]'
}) })
export class PasswordToggleDirective { export class PasswordToggleDirective {
@Input() @Input()
@@ -10,7 +10,10 @@ export class PasswordToggleDirective {
@Input() @Input()
iconId: string; iconId: string;
constructor(private elementRef: ElementRef, private renderer: Renderer2) { constructor(
private elementRef: ElementRef,
private renderer: Renderer2,
) {
this.renderer.listen(this.elementRef.nativeElement, 'click', () => { this.renderer.listen(this.elementRef.nativeElement, 'click', () => {
this.togglePasswordVisibility(); this.togglePasswordVisibility();
}); });

View File

@@ -5,11 +5,11 @@ import { AuthComponent } from '@app/auth/auth.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: AuthComponent }, { path: '', component: AuthComponent },
{ path: '**', redirectTo: '', pathMatch: 'full' }, { path: '**', redirectTo: '', pathMatch: 'full'},
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class AuthRoutingModule {} export class AuthRoutingModule { }

View File

@@ -8,8 +8,9 @@ describe('AuthComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [AuthComponent], declarations: [ AuthComponent ]
}).compileComponents(); })
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,14 +1,14 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { CustomErrorStateMatcher } from '@app/_helpers'; import {CustomErrorStateMatcher} from '@app/_helpers';
import { AuthService } from '@app/_services'; import {AuthService} from '@app/_services';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
templateUrl: './auth.component.html', templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss'], styleUrls: ['./auth.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AuthComponent implements OnInit { export class AuthComponent implements OnInit {
keyForm: FormGroup; keyForm: FormGroup;
@@ -20,7 +20,7 @@ export class AuthComponent implements OnInit {
private authService: AuthService, private authService: AuthService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router private router: Router
) {} ) { }
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.keyForm = this.formBuilder.group({ this.keyForm = this.formBuilder.group({
@@ -33,16 +33,12 @@ export class AuthComponent implements OnInit {
// } // }
} }
get keyFormStub(): any { get keyFormStub(): any { return this.keyForm.controls; }
return this.keyForm.controls;
}
async onSubmit(): Promise<void> { async onSubmit(): Promise<void> {
this.submitted = true; this.submitted = true;
if (this.keyForm.invalid) { if (this.keyForm.invalid) { return; }
return;
}
this.loading = true; this.loading = true;
await this.authService.setKey(this.keyFormStub.key.value); await this.authService.setKey(this.keyFormStub.key.value);

View File

@@ -3,13 +3,14 @@ import { CommonModule } from '@angular/common';
import { AuthRoutingModule } from '@app/auth/auth-routing.module'; import { AuthRoutingModule } from '@app/auth/auth-routing.module';
import { AuthComponent } from '@app/auth/auth.component'; import { AuthComponent } from '@app/auth/auth.component';
import { ReactiveFormsModule } from '@angular/forms'; import {ReactiveFormsModule} from '@angular/forms';
import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive'; import {PasswordToggleDirective} from '@app/auth/_directives/password-toggle.directive';
import { MatCardModule } from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select'; import {MatSelectModule} from '@angular/material/select';
import { MatInputModule } from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import { MatRippleModule } from '@angular/material/core'; import {MatRippleModule} from '@angular/material/core';
@NgModule({ @NgModule({
declarations: [AuthComponent, PasswordToggleDirective], declarations: [AuthComponent, PasswordToggleDirective],
@@ -22,6 +23,6 @@ import { MatRippleModule } from '@angular/material/core';
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
MatRippleModule, MatRippleModule,
], ]
}) })
export class AuthModule {} export class AuthModule { }

View File

@@ -1,19 +1,13 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component'; import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { ActivatedRoute } from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import { AccountsModule } from '@pages/accounts/accounts.module'; import {AccountsModule} from '@pages/accounts/accounts.module';
import { UserService } from '@app/_services'; import {UserService} from '@app/_services';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
import { import {ActivatedRouteStub, FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
ActivatedRouteStub,
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
UserServiceStub,
} from '@src/testing';
describe('AccountDetailsComponent', () => { describe('AccountDetailsComponent', () => {
let component: AccountDetailsComponent; let component: AccountDetailsComponent;
@@ -30,14 +24,19 @@ describe('AccountDetailsComponent', () => {
AccountDetailsComponent, AccountDetailsComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
],
imports: [
AccountsModule,
AppModule,
HttpClientTestingModule,
], ],
imports: [AccountsModule, AppModule, HttpClientTestingModule],
providers: [ providers: [
{ provide: ActivatedRoute, useValue: route }, { provide: ActivatedRoute, useValue: route },
{ provide: UserService, useClass: UserServiceStub }, { provide: UserService, useClass: UserServiceStub }
], ]
}).compileComponents(); })
.compileComponents();
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
}); });

View File

@@ -1,50 +1,37 @@
import { import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
ChangeDetectionStrategy, import {MatTableDataSource} from '@angular/material/table';
ChangeDetectorRef, import {MatPaginator} from '@angular/material/paginator';
Component, import {MatSort} from '@angular/material/sort';
OnInit, import {BlockSyncService, LocationService, LoggingService, TokenService, TransactionService, UserService} from '@app/_services';
ViewChild, import {ActivatedRoute, Params, Router} from '@angular/router';
} from '@angular/core'; import {first} from 'rxjs/operators';
import { MatTableDataSource } from '@angular/material/table'; import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { MatPaginator } from '@angular/material/paginator'; import {copyToClipboard, CustomErrorStateMatcher, exportCsv} from '@app/_helpers';
import { MatSort } from '@angular/material/sort'; import {MatSnackBar} from '@angular/material/snack-bar';
import { import {add0x, strip0x} from '@src/assets/js/ethtx/dist/hex';
BlockSyncService, import {environment} from '@src/environments/environment';
LocationService, import {AccountDetails, AreaName, AreaType, Category, Transaction} from '@app/_models';
LoggingService,
TokenService,
TransactionService,
UserService,
} from '@app/_services';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { first } from 'rxjs/operators';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { copyToClipboard, CustomErrorStateMatcher, exportCsv } from '@app/_helpers';
import { MatSnackBar } from '@angular/material/snack-bar';
import { add0x, strip0x } from '@src/assets/js/ethtx/dist/hex';
import { environment } from '@src/environments/environment';
import { AccountDetails, AreaName, AreaType, Category, Transaction } from '@app/_models';
@Component({ @Component({
selector: 'app-account-details', selector: 'app-account-details',
templateUrl: './account-details.component.html', templateUrl: './account-details.component.html',
styleUrls: ['./account-details.component.scss'], styleUrls: ['./account-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AccountDetailsComponent implements OnInit { export class AccountDetailsComponent implements OnInit {
transactionsDataSource: MatTableDataSource<any>; transactionsDataSource: MatTableDataSource<any>;
transactionsDisplayedColumns: Array<string> = ['sender', 'recipient', 'value', 'created', 'type']; transactionsDisplayedColumns: Array<string> = ['sender', 'recipient', 'value', 'created', 'type'];
transactionsDefaultPageSize: number = 10; transactionsDefaultPageSize: number = 10;
transactionsPageSizeOptions: Array<number> = [10, 20, 50, 100]; transactionsPageSizeOptions: Array<number> = [10, 20, 50, 100];
@ViewChild('TransactionTablePaginator', { static: true }) transactionTablePaginator: MatPaginator; @ViewChild('TransactionTablePaginator', {static: true}) transactionTablePaginator: MatPaginator;
@ViewChild('TransactionTableSort', { static: true }) transactionTableSort: MatSort; @ViewChild('TransactionTableSort', {static: true}) transactionTableSort: MatSort;
userDataSource: MatTableDataSource<any>; userDataSource: MatTableDataSource<any>;
userDisplayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location']; userDisplayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location'];
usersDefaultPageSize: number = 10; usersDefaultPageSize: number = 10;
usersPageSizeOptions: Array<number> = [10, 20, 50, 100]; usersPageSizeOptions: Array<number> = [10, 20, 50, 100];
@ViewChild('UserTablePaginator', { static: true }) userTablePaginator: MatPaginator; @ViewChild('UserTablePaginator', {static: true}) userTablePaginator: MatPaginator;
@ViewChild('UserTableSort', { static: true }) userTableSort: MatSort; @ViewChild('UserTableSort', {static: true}) userTableSort: MatSort;
accountInfoForm: FormGroup; accountInfoForm: FormGroup;
account: AccountDetails; account: AccountDetails;
@@ -76,7 +63,7 @@ export class AccountDetailsComponent implements OnInit {
private loggingService: LoggingService, private loggingService: LoggingService,
private blockSyncService: BlockSyncService, private blockSyncService: BlockSyncService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private snackBar: MatSnackBar private snackBar: MatSnackBar,
) { ) {
this.accountInfoForm = this.formBuilder.group({ this.accountInfoForm = this.formBuilder.group({
name: ['', Validators.required], name: ['', Validators.required],
@@ -92,10 +79,8 @@ export class AccountDetailsComponent implements OnInit {
}); });
this.route.paramMap.subscribe(async (params: Params) => { this.route.paramMap.subscribe(async (params: Params) => {
this.accountAddress = add0x(params.get('id')); this.accountAddress = add0x(params.get('id'));
this.bloxbergLink = this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions'; (await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(async res => {
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
async (res) => {
if (res !== undefined) { if (res !== undefined) {
this.account = res; this.account = res;
this.cdr.detectChanges(); this.cdr.detectChanges();
@@ -117,45 +102,26 @@ export class AccountDetailsComponent implements OnInit {
} else { } else {
alert('Account not found!'); alert('Account not found!');
} }
} });
);
this.blockSyncService.blockSync(this.accountAddress); this.blockSyncService.blockSync(this.accountAddress);
}); });
this.userService this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res);
.getCategories() this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res);
.pipe(first()) this.locationService.getAreaTypes().pipe(first()).subscribe(res => this.areaTypes = res);
.subscribe((res) => (this.categories = res)); this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res);
this.locationService this.userService.getTransactionTypes().pipe(first()).subscribe(res => this.transactionsTypes = res);
.getAreaNames() this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res);
.pipe(first())
.subscribe((res) => (this.areaNames = res));
this.locationService
.getAreaTypes()
.pipe(first())
.subscribe((res) => (this.areaTypes = res));
this.userService
.getAccountTypes()
.pipe(first())
.subscribe((res) => (this.accountTypes = res));
this.userService
.getTransactionTypes()
.pipe(first())
.subscribe((res) => (this.transactionsTypes = res));
this.userService
.getGenders()
.pipe(first())
.subscribe((res) => (this.genders = res));
} }
ngOnInit(): void { ngOnInit(): void {
this.userService.accountsSubject.subscribe((accounts) => { this.userService.accountsSubject.subscribe(accounts => {
this.userDataSource = new MatTableDataSource<any>(accounts); this.userDataSource = new MatTableDataSource<any>(accounts);
this.userDataSource.paginator = this.userTablePaginator; this.userDataSource.paginator = this.userTablePaginator;
this.userDataSource.sort = this.userTableSort; this.userDataSource.sort = this.userTableSort;
this.accounts = accounts; this.accounts = accounts;
}); });
this.transactionService.transactionsSubject.subscribe((transactions) => { this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionsDataSource = new MatTableDataSource<any>(transactions); this.transactionsDataSource = new MatTableDataSource<any>(transactions);
this.transactionsDataSource.paginator = this.transactionTablePaginator; this.transactionsDataSource.paginator = this.transactionTablePaginator;
this.transactionsDataSource.sort = this.transactionTableSort; this.transactionsDataSource.sort = this.transactionTableSort;
@@ -176,20 +142,14 @@ export class AccountDetailsComponent implements OnInit {
} }
viewAccount(account): void { viewAccount(account): void {
this.router.navigateByUrl( this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} }
get accountInfoFormStub(): any { get accountInfoFormStub(): any { return this.accountInfoForm.controls; }
return this.accountInfoForm.controls;
}
async saveInfo(): Promise<void> { async saveInfo(): Promise<void> {
this.submitted = true; this.submitted = true;
if (this.accountInfoForm.invalid || !confirm(`Change user's profile information?`)) { if (this.accountInfoForm.invalid || !confirm('Change user\'s profile information?')) { return; }
return;
}
const accountKey = await this.userService.changeAccountInfo( const accountKey = await this.userService.changeAccountInfo(
this.accountAddress, this.accountAddress,
this.accountInfoFormStub.name.value, this.accountInfoFormStub.name.value,
@@ -208,38 +168,29 @@ export class AccountDetailsComponent implements OnInit {
filterAccounts(): void { filterAccounts(): void {
if (this.accountsType === 'all') { if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe((accounts) => { this.userService.accountsSubject.subscribe(accounts => {
this.userDataSource.data = accounts; this.userDataSource.data = accounts;
this.accounts = accounts; this.accounts = accounts;
}); });
} else { } else {
this.userDataSource.data = this.accounts.filter( this.userDataSource.data = this.accounts.filter(account => account.type === this.accountsType);
(account) => account.type === this.accountsType
);
} }
} }
filterTransactions(): void { filterTransactions(): void {
if (this.transactionsType === 'all') { if (this.transactionsType === 'all') {
this.transactionService.transactionsSubject.subscribe((transactions) => { this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionsDataSource.data = transactions; this.transactionsDataSource.data = transactions;
this.transactions = transactions; this.transactions = transactions;
}); });
} else { } else {
this.transactionsDataSource.data = this.transactions.filter( this.transactionsDataSource.data = this.transactions.filter(transaction => transaction.type === this.transactionsType);
(transaction) => transaction.type === this.transactionsType
);
} }
} }
resetPin(): void { resetPin(): void {
if (!confirm(`Reset user's pin?`)) { if (!confirm('Reset user\'s pin?')) { return; }
return; this.userService.resetPin(this.account.vcard.tel[0].value).pipe(first()).subscribe(res => {
}
this.userService
.resetPin(this.account.vcard.tel[0].value)
.pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
}); });
} }
@@ -250,9 +201,7 @@ export class AccountDetailsComponent implements OnInit {
copyAddress(): void { copyAddress(): void {
if (copyToClipboard(this.accountAddress)) { if (copyToClipboard(this.accountAddress)) {
this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', { this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', { duration: 3000 });
duration: 3000,
});
} }
} }
} }

View File

@@ -8,8 +8,9 @@ describe('AccountSearchComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [AccountSearchComponent], declarations: [ AccountSearchComponent ]
}).compileComponents(); })
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,16 +1,16 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { CustomErrorStateMatcher } from '@app/_helpers'; import {CustomErrorStateMatcher} from '@app/_helpers';
import { UserService } from '@app/_services'; import {UserService} from '@app/_services';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { strip0x } from '@src/assets/js/ethtx/dist/hex'; import {strip0x} from '@src/assets/js/ethtx/dist/hex';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
@Component({ @Component({
selector: 'app-account-search', selector: 'app-account-search',
templateUrl: './account-search.component.html', templateUrl: './account-search.component.html',
styleUrls: ['./account-search.component.scss'], styleUrls: ['./account-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AccountSearchComponent implements OnInit { export class AccountSearchComponent implements OnInit {
nameSearchForm: FormGroup; nameSearchForm: FormGroup;
@@ -27,8 +27,8 @@ export class AccountSearchComponent implements OnInit {
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private userService: UserService, private userService: UserService,
private router: Router private router: Router,
) {} ) { }
ngOnInit(): void { ngOnInit(): void {
this.nameSearchForm = this.formBuilder.group({ this.nameSearchForm = this.formBuilder.group({
@@ -42,21 +42,13 @@ export class AccountSearchComponent implements OnInit {
}); });
} }
get nameSearchFormStub(): any { get nameSearchFormStub(): any { return this.nameSearchForm.controls; }
return this.nameSearchForm.controls; get phoneSearchFormStub(): any { return this.phoneSearchForm.controls; }
} get addressSearchFormStub(): any { return this.addressSearchForm.controls; }
get phoneSearchFormStub(): any {
return this.phoneSearchForm.controls;
}
get addressSearchFormStub(): any {
return this.addressSearchForm.controls;
}
onNameSearch(): void { onNameSearch(): void {
this.nameSearchSubmitted = true; this.nameSearchSubmitted = true;
if (this.nameSearchForm.invalid) { if (this.nameSearchForm.invalid) { return; }
return;
}
this.nameSearchLoading = true; this.nameSearchLoading = true;
this.userService.searchAccountByName(this.nameSearchFormStub.name.value); this.userService.searchAccountByName(this.nameSearchFormStub.name.value);
this.nameSearchLoading = false; this.nameSearchLoading = false;
@@ -64,17 +56,11 @@ export class AccountSearchComponent implements OnInit {
async onPhoneSearch(): Promise<void> { async onPhoneSearch(): Promise<void> {
this.phoneSearchSubmitted = true; this.phoneSearchSubmitted = true;
if (this.phoneSearchForm.invalid) { if (this.phoneSearchForm.invalid) { return; }
return;
}
this.phoneSearchLoading = true; this.phoneSearchLoading = true;
( (await this.userService.getAccountByPhone(this.phoneSearchFormStub.phoneNumber.value, 100)).subscribe(async res => {
await this.userService.getAccountByPhone(this.phoneSearchFormStub.phoneNumber.value, 100)
).subscribe(async (res) => {
if (res !== undefined) { if (res !== undefined) {
await this.router.navigateByUrl( await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} else { } else {
alert('Account not found!'); alert('Account not found!');
} }
@@ -84,17 +70,11 @@ export class AccountSearchComponent implements OnInit {
async onAddressSearch(): Promise<void> { async onAddressSearch(): Promise<void> {
this.addressSearchSubmitted = true; this.addressSearchSubmitted = true;
if (this.addressSearchForm.invalid) { if (this.addressSearchForm.invalid) { return; }
return;
}
this.addressSearchLoading = true; this.addressSearchLoading = true;
( (await this.userService.getAccountByAddress(this.addressSearchFormStub.address.value, 100)).subscribe(async res => {
await this.userService.getAccountByAddress(this.addressSearchFormStub.address.value, 100)
).subscribe(async (res) => {
if (res !== undefined) { if (res !== undefined) {
await this.router.navigateByUrl( await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} else { } else {
alert('Account not found!'); alert('Account not found!');
} }

View File

@@ -2,20 +2,20 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { AccountsComponent } from '@pages/accounts/accounts.component'; import { AccountsComponent } from '@pages/accounts/accounts.component';
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component'; import {CreateAccountComponent} from '@pages/accounts/create-account/create-account.component';
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component'; import {AccountDetailsComponent} from '@pages/accounts/account-details/account-details.component';
import { AccountSearchComponent } from '@pages/accounts/account-search/account-search.component'; import {AccountSearchComponent} from '@pages/accounts/account-search/account-search.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: AccountsComponent }, { path: '', component: AccountsComponent },
{ path: 'search', component: AccountSearchComponent }, { path: 'search', component: AccountSearchComponent },
// { path: 'create', component: CreateAccountComponent }, // { path: 'create', component: CreateAccountComponent },
{ path: ':id', component: AccountDetailsComponent }, { path: ':id', component: AccountDetailsComponent },
{ path: '**', redirectTo: '', pathMatch: 'full' }, { path: '**', redirectTo: '', pathMatch: 'full' }
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class AccountsRoutingModule {} export class AccountsRoutingModule { }

View File

@@ -1,17 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AccountsComponent } from './accounts.component'; import { AccountsComponent } from './accounts.component';
import { import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
FooterStubComponent, import {AccountsModule} from '@pages/accounts/accounts.module';
SidebarStubComponent, import {AppModule} from '@app/app.module';
TopbarStubComponent, import {HttpClient} from '@angular/common/http';
UserServiceStub, import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
} from '@src/testing'; import {UserService} from '@app/_services';
import { AccountsModule } from '@pages/accounts/accounts.module';
import { AppModule } from '@app/app.module';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { UserService } from '@app/_services';
describe('AccountsComponent', () => { describe('AccountsComponent', () => {
let component: AccountsComponent; let component: AccountsComponent;
@@ -25,11 +20,18 @@ describe('AccountsComponent', () => {
AccountsComponent, AccountsComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AccountsModule, AppModule, HttpClientTestingModule], imports: [
providers: [{ provide: UserService, useClass: UserServiceStub }], AccountsModule,
}).compileComponents(); AppModule,
HttpClientTestingModule,
],
providers: [
{ provide: UserService, useClass: UserServiceStub }
]
})
.compileComponents();
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
}); });

View File

@@ -1,20 +1,22 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import {MatTableDataSource} from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import {MatPaginator} from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import {MatSort} from '@angular/material/sort';
import { LoggingService, UserService } from '@app/_services'; import {LoggingService, UserService} from '@app/_services';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { exportCsv } from '@app/_helpers'; import {exportCsv} from '@app/_helpers';
import { strip0x } from '@src/assets/js/ethtx/dist/hex'; import {strip0x} from '@src/assets/js/ethtx/dist/hex';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { environment } from '@src/environments/environment'; import {environment} from '@src/environments/environment';
import { AccountDetails } from '@app/_models'; import {AccountDetails} from '@app/_models';
import {Observable, of} from 'rxjs';
@Component({ @Component({
selector: 'app-accounts', selector: 'app-accounts',
templateUrl: './accounts.component.html', templateUrl: './accounts.component.html',
styleUrls: ['./accounts.component.scss'], styleUrls: ['./accounts.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class AccountsComponent implements OnInit { export class AccountsComponent implements OnInit {
dataSource: MatTableDataSource<any>; dataSource: MatTableDataSource<any>;
@@ -30,25 +32,17 @@ export class AccountsComponent implements OnInit {
constructor( constructor(
private userService: UserService, private userService: UserService,
private loggingService: LoggingService, private router: Router,
private router: Router private loggingService: LoggingService
) { )
(async () => { {
try { this.userService = userService;
// TODO it feels like this should be in the onInit handler
await this.userService.loadAccounts(100);
} catch (error) {
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error });
}
})();
this.userService
.getAccountTypes()
.pipe(first())
.subscribe((res) => (this.accountTypes = res));
} }
ngOnInit(): void { ngOnInit(): void {
this.userService.accountsSubject.subscribe((accounts) => { of(this.userService.load())
this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res);
this.userService.accountsSubject.subscribe(accounts => {
this.dataSource = new MatTableDataSource<any>(accounts); this.dataSource = new MatTableDataSource<any>(accounts);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
@@ -61,19 +55,17 @@ export class AccountsComponent implements OnInit {
} }
async viewAccount(account): Promise<void> { async viewAccount(account): Promise<void> {
await this.router.navigateByUrl( await this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} }
filterAccounts(): void { filterAccounts(): void {
if (this.accountsType === 'all') { if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe((accounts) => { this.userService.accountsSubject.subscribe(accounts => {
this.dataSource.data = accounts; this.dataSource.data = accounts;
this.accounts = accounts; this.accounts = accounts;
}); });
} else { } else {
this.dataSource.data = this.accounts.filter((account) => account.type === this.accountsType); this.dataSource.data = this.accounts.filter(account => account.type === this.accountsType);
} }
} }

View File

@@ -3,38 +3,41 @@ import { CommonModule } from '@angular/common';
import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module'; import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module';
import { AccountsComponent } from '@pages/accounts/accounts.component'; import { AccountsComponent } from '@pages/accounts/accounts.component';
import { SharedModule } from '@app/shared/shared.module'; import {SharedModule} from '@app/shared/shared.module';
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component'; import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
import {DataTablesModule} from 'angular-datatables';
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component'; import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
import { MatTableModule } from '@angular/material/table'; import {MatTableModule} from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort'; import {MatSortModule} from '@angular/material/sort';
import { MatCheckboxModule } from '@angular/material/checkbox'; import {MatCheckboxModule} from '@angular/material/checkbox';
import { MatPaginatorModule } from '@angular/material/paginator'; import {MatPaginatorModule} from '@angular/material/paginator';
import { MatInputModule } from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import { MatSelectModule } from '@angular/material/select'; import {MatSelectModule} from '@angular/material/select';
import { TransactionsModule } from '@pages/transactions/transactions.module'; import {TransactionsModule} from '@pages/transactions/transactions.module';
import { MatTabsModule } from '@angular/material/tabs'; import {MatTabsModule} from '@angular/material/tabs';
import { MatRippleModule } from '@angular/material/core'; import {MatRippleModule} from '@angular/material/core';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import { ReactiveFormsModule } from '@angular/forms'; import {ReactiveFormsModule} from '@angular/forms';
import { AccountSearchComponent } from './account-search/account-search.component'; import { AccountSearchComponent } from './account-search/account-search.component';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import {MatSnackBarModule} from '@angular/material/snack-bar';
@NgModule({ @NgModule({
declarations: [ declarations: [
AccountsComponent, AccountsComponent,
AccountDetailsComponent, AccountDetailsComponent,
CreateAccountComponent, CreateAccountComponent,
AccountSearchComponent, AccountSearchComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
AccountsRoutingModule, AccountsRoutingModule,
SharedModule, SharedModule,
DataTablesModule,
MatTableModule, MatTableModule,
MatSortModule, MatSortModule,
MatCheckboxModule, MatCheckboxModule,
@@ -51,6 +54,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
MatProgressSpinnerModule, MatProgressSpinnerModule,
ReactiveFormsModule, ReactiveFormsModule,
MatSnackBarModule, MatSnackBarModule,
], ]
}) })
export class AccountsModule {} export class AccountsModule { }

View File

@@ -1,9 +1,10 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component'; import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
import { AccountsModule } from '@pages/accounts/accounts.module'; import {AccountsModule} from '@pages/accounts/accounts.module';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing'; import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
describe('CreateAccountComponent', () => { describe('CreateAccountComponent', () => {
let component: CreateAccountComponent; let component: CreateAccountComponent;
@@ -15,10 +16,14 @@ describe('CreateAccountComponent', () => {
CreateAccountComponent, CreateAccountComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AccountsModule, AppModule], imports: [
}).compileComponents(); AccountsModule,
AppModule
]
})
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,15 +1,15 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { LocationService, UserService } from '@app/_services'; import {LocationService, UserService} from '@app/_services';
import { CustomErrorStateMatcher } from '@app/_helpers'; import {CustomErrorStateMatcher} from '@app/_helpers';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { AreaName, Category } from '@app/_models'; import {AreaName, Category} from '@app/_models';
@Component({ @Component({
selector: 'app-create-account', selector: 'app-create-account',
templateUrl: './create-account.component.html', templateUrl: './create-account.component.html',
styleUrls: ['./create-account.component.scss'], styleUrls: ['./create-account.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class CreateAccountComponent implements OnInit { export class CreateAccountComponent implements OnInit {
createForm: FormGroup; createForm: FormGroup;
@@ -24,7 +24,7 @@ export class CreateAccountComponent implements OnInit {
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private locationService: LocationService, private locationService: LocationService,
private userService: UserService private userService: UserService
) {} ) { }
ngOnInit(): void { ngOnInit(): void {
this.createForm = this.formBuilder.group({ this.createForm = this.formBuilder.group({
@@ -37,35 +37,19 @@ export class CreateAccountComponent implements OnInit {
location: ['', Validators.required], location: ['', Validators.required],
gender: ['', Validators.required], gender: ['', Validators.required],
referrer: ['', Validators.required], referrer: ['', Validators.required],
businessCategory: ['', Validators.required], businessCategory: ['', Validators.required]
}); });
this.userService this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res);
.getCategories() this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res);
.pipe(first()) this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res);
.subscribe((res) => (this.categories = res)); this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res);
this.locationService
.getAreaNames()
.pipe(first())
.subscribe((res) => (this.areaNames = res));
this.userService
.getAccountTypes()
.pipe(first())
.subscribe((res) => (this.accountTypes = res));
this.userService
.getGenders()
.pipe(first())
.subscribe((res) => (this.genders = res));
} }
get createFormStub(): any { get createFormStub(): any { return this.createForm.controls; }
return this.createForm.controls;
}
onSubmit(): void { onSubmit(): void {
this.submitted = true; this.submitted = true;
if (this.createForm.invalid || !confirm('Create account?')) { if (this.createForm.invalid || !confirm('Create account?')) { return; }
return;
}
this.submitted = false; this.submitted = false;
} }
} }

View File

@@ -7,6 +7,6 @@ const routes: Routes = [{ path: '', component: AdminComponent }];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class AdminRoutingModule {} export class AdminRoutingModule { }

View File

@@ -1,17 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminComponent } from '@pages/admin/admin.component'; import { AdminComponent } from '@pages/admin/admin.component';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { AdminModule } from '@pages/admin/admin.module'; import {AdminModule} from '@pages/admin/admin.module';
import { import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
FooterStubComponent, import {AppModule} from '@app/app.module';
SidebarStubComponent, import {UserService} from '@app/_services';
TopbarStubComponent,
UserServiceStub,
} from '@src/testing';
import { AppModule } from '@app/app.module';
import { UserService } from '@app/_services';
describe('AdminComponent', () => { describe('AdminComponent', () => {
let component: AdminComponent; let component: AdminComponent;
@@ -26,11 +21,18 @@ describe('AdminComponent', () => {
AdminComponent, AdminComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AdminModule, AppModule, HttpClientTestingModule], imports: [
providers: [{ provide: UserService, useClass: UserServiceStub }], AdminModule,
}).compileComponents(); AppModule,
HttpClientTestingModule,
],
providers: [
{ provide: UserService, useClass: UserServiceStub }
]
})
.compileComponents();
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
userService = new UserServiceStub(); userService = new UserServiceStub();
@@ -53,7 +55,7 @@ describe('AdminComponent', () => {
user: 'Tom', user: 'Tom',
role: 'enroller', role: 'enroller',
action: 'Disburse RSV 100', action: 'Disburse RSV 100',
approval: false, approval: false
}); });
}); });
}); });

View File

@@ -1,12 +1,12 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import {MatTableDataSource} from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import {MatPaginator} from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import {MatSort} from '@angular/material/sort';
import { LoggingService, UserService } from '@app/_services'; import {LoggingService, UserService} from '@app/_services';
import { animate, state, style, transition, trigger } from '@angular/animations'; import {animate, state, style, transition, trigger} from '@angular/animations';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { exportCsv } from '@app/_helpers'; import {exportCsv} from '@app/_helpers';
import { Action } from '../../_models'; import {Action} from '../../_models';
@Component({ @Component({
selector: 'app-admin', selector: 'app-admin',
@@ -15,11 +15,11 @@ import { Action } from '../../_models';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: [ animations: [
trigger('detailExpand', [ trigger('detailExpand', [
state('collapsed', style({ height: '0px', minHeight: 0, visibility: 'hidden' })), state('collapsed', style({height: '0px', minHeight: 0, visibility: 'hidden'})),
state('expanded', style({ height: '*', visibility: 'visible' })), state('expanded', style({height: '*', visibility: 'visible'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]), ])
], ]
}) })
export class AdminComponent implements OnInit { export class AdminComponent implements OnInit {
dataSource: MatTableDataSource<any>; dataSource: MatTableDataSource<any>;
@@ -30,9 +30,12 @@ export class AdminComponent implements OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
constructor(private userService: UserService, private loggingService: LoggingService) { constructor(
private userService: UserService,
private loggingService: LoggingService
) {
this.userService.getActions(); this.userService.getActions();
this.userService.actionsSubject.subscribe((actions) => { this.userService.actionsSubject.subscribe(actions => {
this.dataSource = new MatTableDataSource<any>(actions); this.dataSource = new MatTableDataSource<any>(actions);
this.dataSource.paginator = this.paginator; this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort; this.dataSource.sort = this.sort;
@@ -40,7 +43,8 @@ export class AdminComponent implements OnInit {
}); });
} }
ngOnInit(): void {} ngOnInit(): void {
}
doFilter(value: string): void { doFilter(value: string): void {
this.dataSource.filter = value.trim().toLocaleLowerCase(); this.dataSource.filter = value.trim().toLocaleLowerCase();
@@ -51,24 +55,14 @@ export class AdminComponent implements OnInit {
} }
approveAction(action: any): void { approveAction(action: any): void {
if (!confirm('Approve action?')) { if (!confirm('Approve action?')) { return; }
return; this.userService.approveAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
}
this.userService
.approveAction(action.id)
.pipe(first())
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
this.userService.getActions(); this.userService.getActions();
} }
disapproveAction(action: any): void { disapproveAction(action: any): void {
if (!confirm('Disapprove action?')) { if (!confirm('Disapprove action?')) { return; }
return; this.userService.revokeAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
}
this.userService
.revokeAction(action.id)
.pipe(first())
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
this.userService.getActions(); this.userService.getActions();
} }

View File

@@ -3,16 +3,17 @@ import { CommonModule } from '@angular/common';
import { AdminRoutingModule } from '@pages/admin/admin-routing.module'; import { AdminRoutingModule } from '@pages/admin/admin-routing.module';
import { AdminComponent } from '@pages/admin/admin.component'; import { AdminComponent } from '@pages/admin/admin.component';
import { SharedModule } from '@app/shared/shared.module'; import {SharedModule} from '@app/shared/shared.module';
import { MatCardModule } from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import { MatTableModule } from '@angular/material/table'; import {MatTableModule} from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort'; import {MatSortModule} from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator'; import {MatPaginatorModule} from '@angular/material/paginator';
import { MatButtonModule } from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import { MatRippleModule } from '@angular/material/core'; import {MatRippleModule} from '@angular/material/core';
@NgModule({ @NgModule({
declarations: [AdminComponent], declarations: [AdminComponent],
@@ -28,7 +29,7 @@ import { MatRippleModule } from '@angular/material/core';
MatSortModule, MatSortModule,
MatPaginatorModule, MatPaginatorModule,
MatButtonModule, MatButtonModule,
MatRippleModule, MatRippleModule
], ]
}) })
export class AdminModule {} export class AdminModule { }

View File

@@ -5,32 +5,16 @@ import { PagesComponent } from './pages.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'home', component: PagesComponent }, { path: 'home', component: PagesComponent },
{ { path: 'tx', loadChildren: () => import('@pages/transactions/transactions.module').then(m => m.TransactionsModule) },
path: 'tx', { path: 'settings', loadChildren: () => import('@pages/settings/settings.module').then(m => m.SettingsModule) },
loadChildren: () => { path: 'accounts', loadChildren: () => import('@pages/accounts/accounts.module').then(m => m.AccountsModule) },
import('@pages/transactions/transactions.module').then((m) => m.TransactionsModule), { path: 'tokens', loadChildren: () => import('@pages/tokens/tokens.module').then(m => m.TokensModule) },
}, { path: 'admin', loadChildren: () => import('@pages/admin/admin.module').then(m => m.AdminModule) },
{ { path: '**', redirectTo: 'home', pathMatch: 'full'}
path: 'settings',
loadChildren: () => import('@pages/settings/settings.module').then((m) => m.SettingsModule),
},
{
path: 'accounts',
loadChildren: () => import('@pages/accounts/accounts.module').then((m) => m.AccountsModule),
},
{
path: 'tokens',
loadChildren: () => import('@pages/tokens/tokens.module').then((m) => m.TokensModule),
},
{
path: 'admin',
loadChildren: () => import('@pages/admin/admin.module').then((m) => m.AdminModule),
},
{ path: '**', redirectTo: 'home', pathMatch: 'full' },
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class PagesRoutingModule {} export class PagesRoutingModule { }

View File

@@ -8,8 +8,9 @@ describe('PagesComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [PagesComponent], declarations: [ PagesComponent ]
}).compileComponents(); })
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,13 +1,13 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({ @Component({
selector: 'app-pages', selector: 'app-pages',
templateUrl: './pages.component.html', templateUrl: './pages.component.html',
styleUrls: ['./pages.component.scss'], styleUrls: ['./pages.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class PagesComponent { export class PagesComponent {
url: string = 'https://dashboard.sarafu.network/'; url: string = 'https://dashboard.sarafu.network/';
constructor() {} constructor() { }
} }

View File

@@ -3,12 +3,14 @@ import { CommonModule } from '@angular/common';
import { PagesRoutingModule } from '@pages/pages-routing.module'; import { PagesRoutingModule } from '@pages/pages-routing.module';
import { PagesComponent } from '@pages/pages.component'; import { PagesComponent } from '@pages/pages.component';
import { SharedModule } from '@app/shared/shared.module'; import {SharedModule} from '@app/shared/shared.module';
import { MatButtonModule } from '@angular/material/button'; import {ChartsModule} from 'ng2-charts';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatButtonModule} from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import {MatSelectModule} from '@angular/material/select';
import { MatCardModule } from '@angular/material/card'; import {MatInputModule} from '@angular/material/input';
import {MatCardModule} from '@angular/material/card';
@NgModule({ @NgModule({
declarations: [PagesComponent], declarations: [PagesComponent],
@@ -16,11 +18,12 @@ import { MatCardModule } from '@angular/material/card';
CommonModule, CommonModule,
PagesRoutingModule, PagesRoutingModule,
SharedModule, SharedModule,
ChartsModule,
MatButtonModule, MatButtonModule,
MatFormFieldModule, MatFormFieldModule,
MatSelectModule, MatSelectModule,
MatInputModule, MatInputModule,
MatCardModule, MatCardModule
], ]
}) })
export class PagesModule {} export class PagesModule { }

View File

@@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrganizationComponent } from '@pages/settings/organization/organization.component'; import { OrganizationComponent } from '@pages/settings/organization/organization.component';
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing'; import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
import { SettingsModule } from '@pages/settings/settings.module'; import {SettingsModule} from '@pages/settings/settings.module';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
describe('OrganizationComponent', () => { describe('OrganizationComponent', () => {
let component: OrganizationComponent; let component: OrganizationComponent;
@@ -15,10 +15,14 @@ describe('OrganizationComponent', () => {
OrganizationComponent, OrganizationComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AppModule, SettingsModule], imports: [
}).compileComponents(); AppModule,
SettingsModule,
]
})
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,37 +1,35 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { CustomErrorStateMatcher } from '@app/_helpers'; import {CustomErrorStateMatcher} from '@app/_helpers';
@Component({ @Component({
selector: 'app-organization', selector: 'app-organization',
templateUrl: './organization.component.html', templateUrl: './organization.component.html',
styleUrls: ['./organization.component.scss'], styleUrls: ['./organization.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class OrganizationComponent implements OnInit { export class OrganizationComponent implements OnInit {
organizationForm: FormGroup; organizationForm: FormGroup;
submitted: boolean = false; submitted: boolean = false;
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher(); matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
constructor(private formBuilder: FormBuilder) {} constructor(
private formBuilder: FormBuilder
) { }
ngOnInit(): void { ngOnInit(): void {
this.organizationForm = this.formBuilder.group({ this.organizationForm = this.formBuilder.group({
disbursement: ['', Validators.required], disbursement: ['', Validators.required],
transfer: '', transfer: '',
countryCode: ['', Validators.required], countryCode: ['', Validators.required]
}); });
} }
get organizationFormStub(): any { get organizationFormStub(): any { return this.organizationForm.controls; }
return this.organizationForm.controls;
}
onSubmit(): void { onSubmit(): void {
this.submitted = true; this.submitted = true;
if (this.organizationForm.invalid || !confirm('Set organization information?')) { if (this.organizationForm.invalid || !confirm('Set organization information?')) { return; }
return;
}
this.submitted = false; this.submitted = false;
} }
} }

View File

@@ -2,16 +2,16 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { SettingsComponent } from '@pages/settings/settings.component'; import { SettingsComponent } from '@pages/settings/settings.component';
import { OrganizationComponent } from '@pages/settings/organization/organization.component'; import {OrganizationComponent} from '@pages/settings/organization/organization.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: SettingsComponent }, { path: '', component: SettingsComponent },
{ path: 'organization', component: OrganizationComponent }, { path: 'organization', component: OrganizationComponent },
{ path: '**', redirectTo: '', pathMatch: 'full' }, { path: '**', redirectTo: '', pathMatch: 'full' }
]; ];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class SettingsRoutingModule {} export class SettingsRoutingModule { }

View File

@@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from '@pages/settings/settings.component'; import { SettingsComponent } from '@pages/settings/settings.component';
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing'; import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
import { SettingsModule } from '@pages/settings/settings.module'; import {SettingsModule} from '@pages/settings/settings.module';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
describe('SettingsComponent', () => { describe('SettingsComponent', () => {
let component: SettingsComponent; let component: SettingsComponent;
@@ -15,10 +15,14 @@ describe('SettingsComponent', () => {
SettingsComponent, SettingsComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AppModule, SettingsModule], imports: [
}).compileComponents(); AppModule,
SettingsModule,
]
})
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,16 +1,16 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import {MatTableDataSource} from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import {MatPaginator} from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import {MatSort} from '@angular/material/sort';
import { AuthService } from '@app/_services'; import {AuthService} from '@app/_services';
import { Staff } from '@app/_models/staff'; import {Staff} from '@app/_models/staff';
import { exportCsv } from '@app/_helpers'; import {exportCsv} from '@app/_helpers';
@Component({ @Component({
selector: 'app-settings', selector: 'app-settings',
templateUrl: './settings.component.html', templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'], styleUrls: ['./settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SettingsComponent implements OnInit { export class SettingsComponent implements OnInit {
date: string; date: string;
@@ -21,7 +21,9 @@ export class SettingsComponent implements OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
constructor(private authService: AuthService) {} constructor(
private authService: AuthService
) { }
ngOnInit(): void { ngOnInit(): void {
const d = new Date(); const d = new Date();

View File

@@ -3,21 +3,22 @@ import { CommonModule } from '@angular/common';
import { SettingsRoutingModule } from '@pages/settings/settings-routing.module'; import { SettingsRoutingModule } from '@pages/settings/settings-routing.module';
import { SettingsComponent } from '@pages/settings/settings.component'; import { SettingsComponent } from '@pages/settings/settings.component';
import { SharedModule } from '@app/shared/shared.module'; import {SharedModule} from '@app/shared/shared.module';
import { OrganizationComponent } from '@pages/settings/organization/organization.component'; import { OrganizationComponent } from '@pages/settings/organization/organization.component';
import { MatTableModule } from '@angular/material/table'; import {MatTableModule} from '@angular/material/table';
import { MatSortModule } from '@angular/material/sort'; import {MatSortModule} from '@angular/material/sort';
import { MatPaginatorModule } from '@angular/material/paginator'; import {MatPaginatorModule} from '@angular/material/paginator';
import { MatInputModule } from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import { MatCardModule } from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
import { MatRadioModule } from '@angular/material/radio'; import {MatRadioModule} from '@angular/material/radio';
import { MatCheckboxModule } from '@angular/material/checkbox'; import {MatCheckboxModule} from '@angular/material/checkbox';
import { MatSelectModule } from '@angular/material/select'; import {MatSelectModule} from '@angular/material/select';
import { MatMenuModule } from '@angular/material/menu'; import {MatMenuModule} from '@angular/material/menu';
import { ReactiveFormsModule } from '@angular/forms'; import {ReactiveFormsModule} from '@angular/forms';
@NgModule({ @NgModule({
declarations: [SettingsComponent, OrganizationComponent], declarations: [SettingsComponent, OrganizationComponent],
@@ -37,7 +38,7 @@ import { ReactiveFormsModule } from '@angular/forms';
MatCheckboxModule, MatCheckboxModule,
MatSelectModule, MatSelectModule,
MatMenuModule, MatMenuModule,
ReactiveFormsModule, ReactiveFormsModule
], ]
}) })
export class SettingsModule {} export class SettingsModule { }

View File

@@ -1,17 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TokenDetailsComponent } from '@pages/tokens/token-details/token-details.component'; import { TokenDetailsComponent } from '@pages/tokens/token-details/token-details.component';
import { import {ActivatedRouteStub, FooterStubComponent, SidebarStubComponent, TokenServiceStub, TopbarStubComponent} from '@src/testing';
ActivatedRouteStub, import {ActivatedRoute} from '@angular/router';
FooterStubComponent, import {TokenService} from '@app/_services';
SidebarStubComponent, import {TokensModule} from '@pages/tokens/tokens.module';
TokenServiceStub, import {AppModule} from '@app/app.module';
TopbarStubComponent,
} from '@src/testing';
import { ActivatedRoute } from '@angular/router';
import { TokenService } from '@app/_services';
import { TokensModule } from '@pages/tokens/tokens.module';
import { AppModule } from '@app/app.module';
describe('TokenDetailsComponent', () => { describe('TokenDetailsComponent', () => {
let component: TokenDetailsComponent; let component: TokenDetailsComponent;
@@ -26,14 +20,18 @@ describe('TokenDetailsComponent', () => {
TokenDetailsComponent, TokenDetailsComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
providers: [ providers: [
{ provide: ActivatedRoute, useValue: route }, { provide: ActivatedRoute, useValue: route },
{ provide: TokenService, useClass: TokenServiceStub }, { provide: TokenService, useClass: TokenServiceStub }
], ],
imports: [AppModule, TokensModule], imports: [
}).compileComponents(); AppModule,
TokensModule,
]
})
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,28 +1,30 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router'; import {ActivatedRoute, Params} from '@angular/router';
import { TokenService } from '@app/_services'; import {TokenService} from '@app/_services';
import { first } from 'rxjs/operators'; import {first} from 'rxjs/operators';
import { Token } from '../../../_models'; import {Token} from '../../../_models';
@Component({ @Component({
selector: 'app-token-details', selector: 'app-token-details',
templateUrl: './token-details.component.html', templateUrl: './token-details.component.html',
styleUrls: ['./token-details.component.scss'], styleUrls: ['./token-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TokenDetailsComponent implements OnInit { export class TokenDetailsComponent implements OnInit {
token: Token; token: Token;
constructor(private route: ActivatedRoute, private tokenService: TokenService) { constructor(
private route: ActivatedRoute,
private tokenService: TokenService
) {
this.route.paramMap.subscribe((params: Params) => { this.route.paramMap.subscribe((params: Params) => {
this.tokenService this.tokenService.getTokenBySymbol(params.get('id')).pipe(first()).subscribe(res => {
.getTokenBySymbol(params.get('id'))
.pipe(first())
.subscribe((res) => {
this.token = res; this.token = res;
}); });
}); });
} }
ngOnInit(): void {} ngOnInit(): void {
}
} }

View File

@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { TokensComponent } from '@pages/tokens/tokens.component'; import { TokensComponent } from '@pages/tokens/tokens.component';
import { TokenDetailsComponent } from '@pages/tokens/token-details/token-details.component'; import {TokenDetailsComponent} from '@pages/tokens/token-details/token-details.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: TokensComponent }, { path: '', component: TokensComponent },
@@ -11,6 +11,6 @@ const routes: Routes = [
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class TokensRoutingModule {} export class TokensRoutingModule { }

View File

@@ -1,9 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TokensComponent } from '@pages/tokens/tokens.component'; import { TokensComponent } from '@pages/tokens/tokens.component';
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing'; import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
import { TokensModule } from '@pages/tokens/tokens.module'; import {TokensModule} from '@pages/tokens/tokens.module';
describe('TokensComponent', () => { describe('TokensComponent', () => {
let component: TokensComponent; let component: TokensComponent;
@@ -15,10 +15,14 @@ describe('TokensComponent', () => {
TokensComponent, TokensComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AppModule, TokensModule], imports: [
}).compileComponents(); AppModule,
TokensModule
]
})
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,18 +1,18 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator'; import {MatPaginator} from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import {MatSort} from '@angular/material/sort';
import { LoggingService, TokenService } from '@app/_services'; import {LoggingService, TokenService} from '@app/_services';
import { MatTableDataSource } from '@angular/material/table'; import {MatTableDataSource} from '@angular/material/table';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { exportCsv } from '@app/_helpers'; import {exportCsv} from '@app/_helpers';
import { TokenRegistry } from '../../_eth'; import {TokenRegistry} from '../../_eth';
import { Token } from '../../_models'; import {Token} from '../../_models';
@Component({ @Component({
selector: 'app-tokens', selector: 'app-tokens',
templateUrl: './tokens.component.html', templateUrl: './tokens.component.html',
styleUrls: ['./tokens.component.scss'], styleUrls: ['./tokens.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TokensComponent implements OnInit { export class TokensComponent implements OnInit {
dataSource: MatTableDataSource<any>; dataSource: MatTableDataSource<any>;
@@ -25,7 +25,7 @@ export class TokensComponent implements OnInit {
private tokenService: TokenService, private tokenService: TokenService,
private loggingService: LoggingService, private loggingService: LoggingService,
private router: Router private router: Router
) {} ) { }
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.tokenService.LoadEvent.subscribe(async () => { this.tokenService.LoadEvent.subscribe(async () => {

View File

@@ -4,19 +4,20 @@ import { CommonModule } from '@angular/common';
import { TokensRoutingModule } from '@pages/tokens/tokens-routing.module'; import { TokensRoutingModule } from '@pages/tokens/tokens-routing.module';
import { TokensComponent } from '@pages/tokens/tokens.component'; import { TokensComponent } from '@pages/tokens/tokens.component';
import { TokenDetailsComponent } from '@pages/tokens/token-details/token-details.component'; import { TokenDetailsComponent } from '@pages/tokens/token-details/token-details.component';
import { SharedModule } from '@app/shared/shared.module'; import {SharedModule} from '@app/shared/shared.module';
import { MatTableModule } from '@angular/material/table'; import {MatTableModule} from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator'; import {MatPaginatorModule} from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort'; import {MatSortModule} from '@angular/material/sort';
import { MatPseudoCheckboxModule, MatRippleModule } from '@angular/material/core'; import {MatPseudoCheckboxModule, MatRippleModule} from '@angular/material/core';
import { MatCheckboxModule } from '@angular/material/checkbox'; import {MatCheckboxModule} from '@angular/material/checkbox';
import { MatInputModule } from '@angular/material/input'; import {MatInputModule} from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import {MatIconModule} from '@angular/material/icon';
import { MatSidenavModule } from '@angular/material/sidenav'; import {MatSidenavModule} from '@angular/material/sidenav';
import { MatButtonModule } from '@angular/material/button'; import {MatButtonModule} from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar'; import {MatToolbarModule} from '@angular/material/toolbar';
import { MatCardModule } from '@angular/material/card'; import {MatCardModule} from '@angular/material/card';
@NgModule({ @NgModule({
declarations: [TokensComponent, TokenDetailsComponent], declarations: [TokensComponent, TokenDetailsComponent],
@@ -36,7 +37,7 @@ import { MatCardModule } from '@angular/material/card';
MatButtonModule, MatButtonModule,
MatToolbarModule, MatToolbarModule,
MatCardModule, MatCardModule,
MatRippleModule, MatRippleModule
], ]
}) })
export class TokensModule {} export class TokensModule { }

View File

@@ -8,8 +8,9 @@ describe('TransactionDetailsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [TransactionDetailsComponent], declarations: [ TransactionDetailsComponent ]
}).compileComponents(); })
.compileComponents();
}); });
beforeEach(() => { beforeEach(() => {

View File

@@ -1,15 +1,15 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
import { Router } from '@angular/router'; import {Router} from '@angular/router';
import { TransactionService } from '@app/_services'; import {TransactionService} from '@app/_services';
import { copyToClipboard } from '@app/_helpers'; import {copyToClipboard} from '@app/_helpers';
import { MatSnackBar } from '@angular/material/snack-bar'; import {MatSnackBar} from '@angular/material/snack-bar';
import { strip0x } from '@src/assets/js/ethtx/dist/hex'; import {strip0x} from '@src/assets/js/ethtx/dist/hex';
@Component({ @Component({
selector: 'app-transaction-details', selector: 'app-transaction-details',
templateUrl: './transaction-details.component.html', templateUrl: './transaction-details.component.html',
styleUrls: ['./transaction-details.component.scss'], styleUrls: ['./transaction-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TransactionDetailsComponent implements OnInit { export class TransactionDetailsComponent implements OnInit {
@Input() transaction; @Input() transaction;
@@ -20,18 +20,15 @@ export class TransactionDetailsComponent implements OnInit {
constructor( constructor(
private router: Router, private router: Router,
private transactionService: TransactionService, private transactionService: TransactionService,
private snackBar: MatSnackBar private snackBar: MatSnackBar,
) {} ) { }
ngOnInit(): void { ngOnInit(): void {
if (this.transaction?.type === 'conversion') { if (this.transaction?.type === 'conversion') {
this.traderBloxbergLink = this.traderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions';
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions';
} else { } else {
this.senderBloxbergLink = this.senderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.from + '/transactions';
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.from + '/transactions'; this.recipientBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions';
this.recipientBloxbergLink =
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.to + '/transactions';
} }
} }

View File

@@ -7,6 +7,6 @@ const routes: Routes = [{ path: '', component: TransactionsComponent }];
@NgModule({ @NgModule({
imports: [RouterModule.forChild(routes)], imports: [RouterModule.forChild(routes)],
exports: [RouterModule], exports: [RouterModule]
}) })
export class TransactionsRoutingModule {} export class TransactionsRoutingModule { }

View File

@@ -1,11 +1,11 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TransactionsComponent } from '@pages/transactions/transactions.component'; import { TransactionsComponent } from '@pages/transactions/transactions.component';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing'; import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
import { TransactionsModule } from '@pages/transactions/transactions.module'; import {TransactionsModule} from '@pages/transactions/transactions.module';
import { AppModule } from '@app/app.module'; import {AppModule} from '@app/app.module';
describe('TransactionsComponent', () => { describe('TransactionsComponent', () => {
let component: TransactionsComponent; let component: TransactionsComponent;
@@ -19,10 +19,15 @@ describe('TransactionsComponent', () => {
TransactionsComponent, TransactionsComponent,
FooterStubComponent, FooterStubComponent,
SidebarStubComponent, SidebarStubComponent,
TopbarStubComponent, TopbarStubComponent
], ],
imports: [AppModule, HttpClientTestingModule, TransactionsModule], imports: [
}).compileComponents(); AppModule,
HttpClientTestingModule,
TransactionsModule
]
})
.compileComponents();
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
}); });

View File

@@ -1,23 +1,17 @@
import { import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
AfterViewInit, import {BlockSyncService, TransactionService, UserService} from '@app/_services';
ChangeDetectionStrategy, import {MatTableDataSource} from '@angular/material/table';
Component, import {MatPaginator} from '@angular/material/paginator';
OnInit, import {MatSort} from '@angular/material/sort';
ViewChild, import {exportCsv} from '@app/_helpers';
} from '@angular/core'; import {first} from 'rxjs/operators';
import { BlockSyncService, TransactionService, UserService } from '@app/_services'; import {Transaction} from '@app/_models';
import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { exportCsv } from '@app/_helpers';
import { first } from 'rxjs/operators';
import { Transaction } from '@app/_models';
@Component({ @Component({
selector: 'app-transactions', selector: 'app-transactions',
templateUrl: './transactions.component.html', templateUrl: './transactions.component.html',
styleUrls: ['./transactions.component.scss'], styleUrls: ['./transactions.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class TransactionsComponent implements OnInit, AfterViewInit { export class TransactionsComponent implements OnInit, AfterViewInit {
transactionDataSource: MatTableDataSource<any>; transactionDataSource: MatTableDataSource<any>;
@@ -41,16 +35,13 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.transactionService.transactionsSubject.subscribe((transactions) => { this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionDataSource = new MatTableDataSource<any>(transactions); this.transactionDataSource = new MatTableDataSource<any>(transactions);
this.transactionDataSource.paginator = this.paginator; this.transactionDataSource.paginator = this.paginator;
this.transactionDataSource.sort = this.sort; this.transactionDataSource.sort = this.sort;
this.transactions = transactions; this.transactions = transactions;
}); });
this.userService this.userService.getTransactionTypes().pipe(first()).subscribe(res => this.transactionsTypes = res);
.getTransactionTypes()
.pipe(first())
.subscribe((res) => (this.transactionsTypes = res));
} }
viewTransaction(transaction): void { viewTransaction(transaction): void {
@@ -63,14 +54,12 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
filterTransactions(): void { filterTransactions(): void {
if (this.transactionsType === 'all') { if (this.transactionsType === 'all') {
this.transactionService.transactionsSubject.subscribe((transactions) => { this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionDataSource.data = transactions; this.transactionDataSource.data = transactions;
this.transactions = transactions; this.transactions = transactions;
}); });
} else { } else {
this.transactionDataSource.data = this.transactions.filter( this.transactionDataSource.data = this.transactions.filter(transaction => transaction.type === this.transactionsType);
(transaction) => transaction.type === this.transactionsType
);
} }
} }

View File

@@ -4,26 +4,31 @@ import { CommonModule } from '@angular/common';
import { TransactionsRoutingModule } from '@pages/transactions/transactions-routing.module'; import { TransactionsRoutingModule } from '@pages/transactions/transactions-routing.module';
import { TransactionsComponent } from '@pages/transactions/transactions.component'; import { TransactionsComponent } from '@pages/transactions/transactions.component';
import { TransactionDetailsComponent } from '@pages/transactions/transaction-details/transaction-details.component'; import { TransactionDetailsComponent } from '@pages/transactions/transaction-details/transaction-details.component';
import { SharedModule } from '@app/shared/shared.module'; import {DataTablesModule} from 'angular-datatables';
import { MatTableModule } from '@angular/material/table'; import {SharedModule} from '@app/shared/shared.module';
import { MatCheckboxModule } from '@angular/material/checkbox'; import {MatTableModule} from '@angular/material/table';
import { MatPaginatorModule } from '@angular/material/paginator'; import {MatCheckboxModule} from '@angular/material/checkbox';
import { MatSortModule } from '@angular/material/sort'; import {MatPaginatorModule} from '@angular/material/paginator';
import { MatFormFieldModule } from '@angular/material/form-field'; import {MatSortModule} from '@angular/material/sort';
import { MatInputModule } from '@angular/material/input'; import {MatFormFieldModule} from '@angular/material/form-field';
import { MatButtonModule } from '@angular/material/button'; import {MatInputModule} from '@angular/material/input';
import { MatIconModule } from '@angular/material/icon'; import {MatButtonModule} from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select'; import {MatIconModule} from '@angular/material/icon';
import { MatCardModule } from '@angular/material/card'; import {MatSelectModule} from '@angular/material/select';
import { MatRippleModule } from '@angular/material/core'; import {MatCardModule} from '@angular/material/card';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import {MatRippleModule} from '@angular/material/core';
import {MatSnackBarModule} from '@angular/material/snack-bar';
@NgModule({ @NgModule({
declarations: [TransactionsComponent, TransactionDetailsComponent], declarations: [TransactionsComponent, TransactionDetailsComponent],
exports: [TransactionDetailsComponent], exports: [
TransactionDetailsComponent
],
imports: [ imports: [
CommonModule, CommonModule,
TransactionsRoutingModule, TransactionsRoutingModule,
DataTablesModule,
SharedModule, SharedModule,
MatTableModule, MatTableModule,
MatCheckboxModule, MatCheckboxModule,
@@ -37,6 +42,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
MatCardModule, MatCardModule,
MatRippleModule, MatRippleModule,
MatSnackBarModule, MatSnackBarModule,
], ]
}) })
export class TransactionsModule {} export class TransactionsModule { }

Some files were not shown because too many files have changed in this diff Show More