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
198 changed files with 3111 additions and 83527 deletions

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@
/dist
/tmp
/out-tsc
/out
# Only exists if Bazel was run
/bazel-out

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
Run `ng serve` for a local server, `npm run start:dev` for a dev server and `npm run start:prod` for a prod server..
Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
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.
## Code scaffolding
@@ -24,21 +22,14 @@ Run `ng generate module module-name --route module-name --module app.module` to
## Build
Run `ng build` to build the project using local configurations.
The build artifacts will be stored in the `dist/` directory.
set you environment variables - set these via environment variables as found in set-env.ts
// 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.
## 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.
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.
## 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
@@ -46,19 +37,11 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac
## Environment variables
Default environment variables are located in the `src/environments/` directory.
Custom environment variables are contained in the `.env` file. See `.env.example` for a template.
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/`.
It contains environment variables for development on `environment.dev.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.
It contains environment variables for development on `environment.ts` and production on `environment.prod.ts`.
## Further help

View File

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

View File

@@ -35,7 +35,7 @@ http {
location / {
root /usr/share/nginx/html;
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)"
]
}
}
]
}

29493
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,6 @@
{
"name": "cic-staff-client",
"version": "0.0.1",
"author": "Spencer Ofwiti <maxspencer56@gmail.com>",
"description": "A fully featured admin client for managing users and transactions in the CIC network.",
"main": "dist/main.js",
"license": "GPL-3.0-or-later",
"version": "0.0.0",
"scripts": {
"config:dev": "ts-node set-env.ts --environment=dev",
"config:prod": "ts-node set-env.ts --environment=prod",
@@ -12,27 +8,10 @@
"start:prod": "ng serve --prod",
"build:dev": "ng build -c dev",
"build:prod": "ng build --prod",
"start:pwa": "npm run build:prod && http-server -p 4200 dist/cic-staff-client",
"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",
"test:dev": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"precommit": "npm run format:fix && npm run lint",
"postinstall": "node patch-webpack.js",
"prepare": "husky install",
"electron": "ng build --base-href ./ && electron .",
"electron-tsc": "ng build --base-href ./ && tsc --p src-backend --outDir dist && electron .",
"electron:build:dev": "ng build -c dev --base-href ./ && tsc --lib ES2018,DOM --target ES5 src-backend/main.ts --outDir dist && electron .",
"electron:package": "npx electron-forge import && npm run make",
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"elec:package": "npm run build:dev && electron-packager . CICADA --out out --overwrite --asar --icon=dist/cic-staff-client/icons/manifest-icon-512.ico --ignore=^e2e$ --ignore=^src$ --ignore=^src-backend$ --ignore=^.editorconfig$ --ignore=^.gitignore$ --ignore=^angular.json$ --ignore=^browserslist$ --ignore=^karma.conf.js$ --ignore=^package-lock.json$ --ignore=^README.md$ --ignore=^tslint --ignore=^tsconfig --all",
"clean": "rimraf dist",
"prebuild": "npm run clean"
"postinstall": "node patch-webpack.js"
},
"private": true,
"dependencies": {
@@ -46,21 +25,29 @@
"@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0",
"@angular/service-worker": "~10.2.0",
"@cicnet/schemas-data-validator": "*",
"@popperjs/core": "^2.5.4",
"angular-datatables": "^9.0.2",
"block-syncer": "^0.2.4",
"bootstrap": "^4.5.3",
"chart.js": "^2.9.4",
"cic-client": "0.1.4",
"cic-client-meta": "0.0.7-alpha.6",
"electron-squirrel-startup": "^1.0.0",
"cic-schemas-data-validator": "^1.0.0-alpha.3",
"datatables.net": "^1.10.22",
"datatables.net-dt": "^1.10.22",
"ethers": "^5.0.31",
"http-server": "^0.12.3",
"jquery": "^3.5.1",
"mocha": "^8.2.1",
"moolb": "^0.1.0",
"ng2-charts": "^2.4.2",
"ngx-logger": "^4.2.1",
"openpgp": "^4.10.10",
"popper.js": "^1.16.1",
"rxjs": "~6.6.0",
"sha3": "^2.1.4",
"tslib": "^2.0.0",
"vcard-parser": "^1.0.0",
"vcards-js": "^2.10.0",
"web3": "^1.3.0",
"zone.js": "~0.10.2"
},
@@ -68,22 +55,13 @@
"@angular-devkit/build-angular": "~0.1002.0",
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.0",
"@electron-forge/cli": "^6.0.0-beta.55",
"@electron-forge/maker-deb": "^6.0.0-beta.55",
"@electron-forge/maker-rpm": "^6.0.0-beta.55",
"@electron-forge/maker-squirrel": "^6.0.0-beta.55",
"@electron-forge/maker-zip": "^6.0.0-beta.55",
"@types/datatables.net": "^1.10.19",
"@types/electron": "^1.6.10",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/jquery": "^3.5.4",
"@types/node": "^15.6.1",
"@types/node": "^12.20.6",
"codelyzer": "^6.0.0",
"dotenv": "^8.2.0",
"electron": "^12.0.9",
"electron-packager": "^15.2.0",
"husky": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
@@ -92,49 +70,11 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-junit-reporter": "^2.0.1",
"prettier": "^2.3.0",
"pretty-quick": "^3.1.0",
"protractor": "~7.0.0",
"rimraf": "^3.0.2",
"secp256k1": "^4.0.2",
"ts-node": "~8.3.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",
"yargs": "^13.3.2"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged & ng lint"
}
},
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "cic_staff_client"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
]
}
}
}

View File

@@ -6,7 +6,7 @@ require('dotenv').config();
const environment = argv.environment;
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';
@@ -15,13 +15,13 @@ export const environment = {
bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996},
logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'},
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'}',
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'}',
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'}',
registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0xea6225212005e86a4490018ded4bf37f3e772161'}',
registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0x6Ca3cB14aA6F761712E1C18646AfBA4d5Ae249E8'}',
trustedDeclaratorAddress: '${process.env.CIC_TRUSTED_ADDRESS || '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C'}'
};
`;

View File

@@ -1,139 +0,0 @@
import { BrowserWindow, Menu, Tray, app, ipcMain } from 'electron';
import * as path from 'path';
import * as url from 'url';
import * as fs from 'fs';
let mainWindow: BrowserWindow;
async function createWindow(): Promise<void> {
// Create the browser window.
mainWindow = new BrowserWindow({
fullscreen: false,
width: 1000,
height: 800,
icon: path.join(__dirname, '/cic-staff-client/assets/icons/manifest-icon-512.png'),
// Allows IPC and other APIs
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
nodeIntegrationInWorker: true,
}
});
// load the dist folder from Angular
await mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, '/cic-staff-client/index.html'),
protocol: 'file',
slashes: true
})
);
// Open the DevTools.
// mainWindow.webContents.openDevTools();
mainWindow.on('closed', () => {
mainWindow = null;
});
}
async function openModal(): Promise<void> {
const modal = new BrowserWindow({
parent: mainWindow,
modal: true,
show: false,
});
await modal.loadURL('https://dashboard.sarafu.network/');
modal.once('ready-to-show', () => {
modal.show();
});
}
function isRoot(): boolean {
return path.parse(process.cwd()).root === process.cwd();
}
function getImages(): void {
const cwd = process.cwd();
fs.readdir('.', { withFileTypes: true }, ((err, files) => {
if (!err) {
const re = /(?:\.([^.]+))?$/;
const images = files.filter(file => file.isFile() && ['jpg', 'png'].includes(re.exec(file.name)[1])).map(file => `file://${cwd}/${file.name}`);
mainWindow.webContents.send('getImagesResponse', images);
}
}));
}
function getDirectory(): void {
fs.readdir('.', { withFileTypes: true }, (err, files) => {
if (!err) {
const directories = files.filter(file => file.isDirectory()).map(file => file.name);
if (!isRoot()) {
directories.unshift('..');
}
mainWindow.webContents.send('getDirectoryResponse', directories);
}
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
let tray = null;
tray = new Tray(path.join(__dirname, '/cic-staff-client/assets/icons/manifest-icon-512.png'));
const contextMenu = Menu.buildFromTemplate([
{ label: 'Item1', type: 'radio' },
{ label: 'Item2', type: 'radio' },
{ label: 'Item3', type: 'radio', checked: true },
{ label: 'Exit', type: 'normal', click: () => { app.quit(); } },
]);
tray.setToolTip('CIC Administration Dashboard.');
// Overrides 'right-click' event.
tray.setContextMenu(contextMenu);
tray.on('click', (event, arg) => {
console.log('Systray was left-clicked.', event, arg);
});
tray.on('double-click', (event, arg) => {
console.log('Systray was double-clicked.', event, arg);
});
await createWindow();
});
app.on('activate', async () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
await createWindow();
}
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
ipcMain.on('my-custom-signal', (event, arg) => {
console.log('Print to the main process terminal (STDOUT) when signal received from renderer process.');
console.log(event);
console.log(arg);
mainWindow.webContents.send('other-custom-signal', 'message from the backend process');
});
ipcMain.on('navigateDirectory', (event, arg) => {
process.chdir(arg);
getImages();
getDirectory();
});
ipcMain.on('getFiles', (event, arg) => {
const files = fs.readdirSync(__dirname);
mainWindow.webContents.send('getFilesResponse', files);
});
ipcMain.on('openModal', async (event, arg) => {
await openModal();
});

View File

@@ -1,9 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../dist",
"module": "commonjs",
"skipLibCheck": true
}
}

View File

@@ -1,8 +1,8 @@
import {environment} from '@src/environments/environment';
import Web3 from 'web3';
import { Web3Service } from '@app/_services/web3.service';
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountsIndex.json');
const web3: Web3 = Web3Service.getInstance();
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountRegistry.json');
const web3: Web3 = new Web3(environment.web3Provider);
export class AccountIndex {
contractAddress: string;
@@ -20,29 +20,29 @@ export class AccountIndex {
}
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> {
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>> {
const count: number = await this.totalAccounts();
let lowest: number = count - numberOfAccounts;
let lowest: number = count - numberOfAccounts - 1;
if (lowest < 0) {
lowest = 0;
}
const accounts: Array<string> = [];
for (let i = count - 1; i >= lowest; i--) {
const account: string = await this.contract.methods.entry(i).call();
for (let i = count - 1; i > lowest; i--) {
const account: string = await this.contract.methods.accounts(i).call();
accounts.push(account);
}
return accounts;
}
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 true;

View File

@@ -1,8 +1,8 @@
import Web3 from 'web3';
import { Web3Service } from '@app/_services/web3.service';
import {environment} from '@src/environments/environment';
const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json');
const web3: Web3 = Web3Service.getInstance();
const web3: Web3 = new Web3(environment.web3Provider);
export class TokenRegistry {
contractAddress: string;

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,10 @@ export class HttpError extends Error {
export class GlobalErrorHandler extends ErrorHandler {
private sentencesForWarningLogging: Array<string> = [];
constructor(private loggingService: LoggingService, private router: Router) {
constructor(
private loggingService: LoggingService,
private router: Router
) {
super();
}
@@ -45,30 +48,14 @@ export class GlobalErrorHandler extends ErrorHandler {
const route: string = this.router.url;
if (error instanceof HttpErrorResponse) {
this.loggingService.sendErrorLevelMessage(
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${
(error as HttpErrorResponse).status
}`,
this,
{ error }
);
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`,
this, {error});
} else if (error instanceof TypeError) {
this.loggingService.sendErrorLevelMessage(
`There was a Type error on route ${route}.\n${error.message}`,
this,
{ error }
);
this.loggingService.sendErrorLevelMessage(`There was a Type error on route ${route}.\n${error.message}`, this, {error});
} else if (error instanceof Error) {
this.loggingService.sendErrorLevelMessage(
`There was a general error on route ${route}.\n${error.message}`,
this,
{ error }
);
this.loggingService.sendErrorLevelMessage(`There was a general error on route ${route}.\n${error.message}`, this, {error});
} else {
this.loggingService.sendErrorLevelMessage(
`Nobody threw an error but something happened on route ${route}!`,
this,
{ error }
);
this.loggingService.sendErrorLevelMessage(`Nobody threw an error but something happened on route ${route}!`, this, {error});
}
}
@@ -87,10 +74,3 @@ export class GlobalErrorHandler extends ErrorHandler {
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 {}
HttpGetter.prototype.get = (filename) =>
new Promise((resolve, reject) => {
fetch(filename).then((response) => {
if (response.ok) {
resolve(response.text());
} else {
reject(rejectBody(response));
}
HttpGetter.prototype.get = filename => new Promise((resolve, reject) => {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.addEventListener('load', (e) => {
if (xhr.status === 200) {
resolve(xhr.responseText);
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,
dataFile: [],
dataFile: []
};
function readCsv(input: any): Array<any> | void {
if (input.files && input.files[0]) {
const reader: FileReader = new FileReader();
reader.readAsBinaryString(input.files[0]);
reader.onload = (event) => {
reader.onload = event => {
objCsv.size = event.total;
objCsv.dataFile = event.target.result;
return parseData(objCsv.dataFile);
@@ -18,11 +18,13 @@ function readCsv(input: any): Array<any> | void {
function parseData(data: any): Array<any> {
const csvData: Array<any> = [];
const lineBreak: Array<any> = data.split('\n');
lineBreak.forEach((res) => {
lineBreak.forEach(res => {
csvData.push(res.split(','));
});
console.table(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> {
const personValidationErrors: any = await validatePerson(person);
if (personValidationErrors) {
personValidationErrors.map((error) => console.error(`${error.message}`, person, error));
personValidationErrors.map(error => console.error(`${error.message}`));
}
}
@@ -12,8 +12,11 @@ async function vcardValidation(vcard: any): Promise<void> {
const vcardValidationErrors: any = await validateVcard(vcard);
if (vcardValidationErrors) {
vcardValidationErrors.map((error) => console.error(`${error.message}`, vcard, error));
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';
describe('ErrorInterceptor', () => {
beforeEach(() =>
TestBed.configureTestingModule({
providers: [ErrorInterceptor],
})
);
beforeEach(() => TestBed.configureTestingModule({
providers: [
ErrorInterceptor
]
}));
it('should be created', () => {
const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor);

View File

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

View File

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

View File

@@ -1,9 +1,15 @@
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';
@Injectable()
export class HttpConfigInterceptor implements HttpInterceptor {
constructor() {}
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';
describe('LoggingInterceptor', () => {
beforeEach(() =>
TestBed.configureTestingModule({
providers: [LoggingInterceptor],
})
);
beforeEach(() => TestBed.configureTestingModule({
providers: [
LoggingInterceptor
]
}));
it('should be created', () => {
const interceptor: LoggingInterceptor = TestBed.inject(LoggingInterceptor);

View File

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

View File

@@ -20,34 +20,24 @@ interface AccountDetails {
products: string[];
category?: string;
vcard: {
email: [
{
email: [{
value: string;
}
];
fn: [
{
}];
fn: [{
value: string;
}
];
n: [
{
}];
n: [{
value: string[];
}
];
tel: [
{
}];
tel: [{
meta: {
TYP: string[];
};
},
value: string;
}
];
version: [
{
}],
version: [{
value: string;
}
];
}];
};
}
@@ -85,35 +75,31 @@ const defaultAccount: AccountDetails = {
},
products: [],
vcard: {
email: [
{
email: [{
value: '',
},
],
fn: [
{
}],
fn: [{
value: 'Sarafu Contract',
},
],
n: [
{
}],
n: [{
value: ['Sarafu', 'Contract'],
},
],
tel: [
{
}],
tel: [{
meta: {
TYP: [],
},
value: '',
},
],
version: [
{
}],
version: [{
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>;
}
export { Action, Category, AreaName, AreaType };
export {
Action,
Category,
AreaName,
AreaType
};

View File

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

View File

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

View File

@@ -4,14 +4,16 @@ interface Token {
address: string;
supply: string;
decimals: string;
reserves?: {
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
weight: string;
balance: string;
};
}
};
reserveRatio?: string;
owner?: string;
}
export { Token };
export {
Token
};

View File

@@ -43,4 +43,10 @@ class Conversion {
tx: Tx;
}
export { BlocksBloom, TxToken, Tx, Transaction, Conversion };
export {
BlocksBloom,
TxToken,
Tx,
Transaction,
Conversion
};

View File

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

View File

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

View File

@@ -6,21 +6,15 @@ import { LoggingService } from '@app/_services/logging.service';
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp';
import {ErrorDialogService} from '@app/_services/error-dialog.service';
import {HttpClient} from '@angular/common/http';
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
import { Staff } from '@app/_models';
import { BehaviorSubject, Observable } from 'rxjs';
import {HttpError} from '@app/_helpers/global-error-handler';
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class AuthService {
sessionToken: any;
sessionLoginCount: number = 0;
mutableKeyStore: MutableKeyStore;
trustedUsers: Array<Staff> = [];
private trustedUsersList: BehaviorSubject<Array<Staff>> = new BehaviorSubject<Array<Staff>>(
this.trustedUsers
);
trustedUsersSubject: Observable<Array<Staff>> = this.trustedUsersList.asObservable();
constructor(
private httpClient: HttpClient,
@@ -45,75 +39,74 @@ export class AuthService {
document.getElementById('state').innerHTML = s;
}
getWithToken(): Promise<boolean> {
return new Promise((resolve, reject) => {
const headers = {
Authorization: 'Bearer ' + this.sessionToken,
'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'none',
};
const options = {
headers,
};
fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) {
return reject(rejectBody(response));
getWithToken(): void {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'text';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.setRequestHeader('Authorization', 'Bearer ' + this.sessionToken);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('x-cic-automerge', 'none');
xhr.addEventListener('load', (e) => {
if (xhr.status === 401) {
throw new Error('login rejected');
}
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
sendResponse(hobaResponseEncoded: any): Promise<boolean> {
return new Promise((resolve, reject) => {
const headers = {
Authorization: 'HOBA ' + hobaResponseEncoded,
'Content-Type': 'application/json;charset=utf-8',
'x-cic-automerge': 'none',
};
const options = {
headers,
};
fetch(environment.cicMetaUrl, options).then((response) => {
if (response.status === 401) {
return reject(rejectBody(response));
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'text';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.setRequestHeader('Authorization', 'HOBA ' + hobaResponseEncoded);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('x-cic-automerge', 'none');
xhr.addEventListener('load', (e) => {
if (xhr.status !== 200) {
const error = new HttpError(xhr.statusText, xhr.status);
return reject(error);
}
this.sessionToken = response.headers.get('Token');
this.sessionToken = xhr.getResponseHeader('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
this.sessionLoginCount++;
this.setState('Click button to log in');
return resolve(true);
});
xhr.send();
});
}
getChallenge(): Promise<any> {
return new Promise((resolve, reject) => {
fetch(environment.cicMetaUrl).then(async (response) => {
if (response.status === 401) {
const authHeader: string = response.headers.get('WWW-Authenticate');
return resolve(hobaParseChallengeHeader(authHeader));
getChallenge(): void {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.responseType = 'arraybuffer';
xhr.open('GET', environment.cicMetaUrl + window.location.search.substring(1));
xhr.onload = async (e) => {
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) {
try {
const response: boolean = await this.getWithToken();
return response === true;
this.getWithToken();
return true;
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login token failed', this, {error: e});
}
} else {
try {
const o = await this.getChallenge();
const response: boolean = await this.loginResponse(o);
return response === true;
this.getChallenge();
} catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e});
}
@@ -121,36 +114,31 @@ export class AuthService {
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) => {
try {
const r = await signChallenge(
o.challenge,
const r = await signChallenge(o.challenge,
o.realm,
environment.cicMetaUrl,
this.mutableKeyStore
);
const response: boolean = await this.sendResponse(r);
resolve(response);
this.mutableKeyStore);
const sessionTokenResult: boolean = await this.sendResponse(r);
} catch (error) {
if (error instanceof HttpError) {
if (error.status === 403) {
this.errorDialogService.openDialog({ message: 'You are not authorized to use this system' });
}
if (error.status === 401) {
this.errorDialogService.openDialog({
message: 'You are not authorized to use this system',
});
} else if (error.status === 401) {
this.errorDialogService.openDialog({
message:
'Unable to authenticate with the service. ' +
message: 'Unable to authenticate with the service. ' +
'Please speak with the staff at Grassroots ' +
'Economics for requesting access ' +
'staff@grassrootseconomics.net.',
'staff@grassrootseconomics.net.'
});
}
} else {
}
// TODO define this error
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'});
}
resolve(false);
}
});
@@ -176,11 +164,7 @@ export class AuthService {
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
} catch (err) {
this.loggingService.sendErrorLevelMessage(
`Failed to set key: ${err.message || err.statusText}`,
this,
{ error: err }
);
this.loggingService.sendErrorLevelMessage(`Failed to set key: ${err.message || err.statusText}`, this, {error: err});
this.errorDialogService.openDialog({
message: `Failed to set key: ${err.message || err.statusText}`,
});
@@ -192,46 +176,28 @@ export class AuthService {
logout(): void {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
localStorage.removeItem(btoa('CICADA_PRIVATE_KEY'));
this.sessionToken = undefined;
window.location.reload();
window.location.reload(true);
}
addTrustedUser(user: Staff): void {
const savedIndex = this.trustedUsers.findIndex((staff) => staff.userid === user.userid);
if (savedIndex === 0) {
return;
}
if (savedIndex > 0) {
this.trustedUsers.splice(savedIndex, 1);
}
this.trustedUsers.unshift(user);
this.trustedUsersList.next(this.trustedUsers);
}
getTrustedUsers(): void {
this.mutableKeyStore.getPublicKeys().forEach((key) => {
this.addTrustedUser(key.users[0].userId);
});
getTrustedUsers(): any {
const trustedUsers: Array<any> = [];
this.mutableKeyStore.getPublicKeys().forEach(key => trustedUsers.push(key.users[0].userId));
return trustedUsers;
}
async getPublicKeys(): Promise<any> {
return new Promise((resolve, reject) => {
fetch(environment.publicKeysUrl).then((res) => {
return await fetch(environment.publicKeysUrl)
.then(res => {
if (!res.ok) {
// TODO does angular recommend an error interface?
return reject(rejectBody(res));
throw Error(`${res.statusText} - ${res.status}`);
}
return resolve(res.text());
});
return res.text();
});
}
getPrivateKey(): any {
return this.mutableKeyStore.getPrivateKey();
}
getPrivateKeyInfo(): any {
return this.getPrivateKey().users[0].userId;
}
}

View File

@@ -9,7 +9,9 @@ describe('BlockSyncService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{ provide: TransactionService, useClass: TransactionServiceStub }],
providers: [
{ provide: TransactionService, useClass: TransactionServiceStub }
]
});
service = TestBed.inject(BlockSyncService);
});

View File

@@ -6,10 +6,9 @@ import { TransactionService } from '@app/_services/transaction.service';
import {environment} from '@src/environments/environment';
import {LoggingService} from '@app/_services/logging.service';
import {RegistryService} from '@app/_services/registry.service';
import { Web3Service } from '@app/_services/web3.service';
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class BlockSyncService {
readyStateTarget: number = 2;
@@ -17,43 +16,34 @@ export class BlockSyncService {
constructor(
private transactionService: TransactionService,
private loggingService: LoggingService
private loggingService: LoggingService,
private registryService: RegistryService,
) { }
async init(): Promise<void> {
await this.transactionService.init();
}
async blockSync(address: string = null, offset: number = 0, limit: number = 100): Promise<void> {
blockSync(address: string = null, offset: number = 0, limit: number = 100): void {
this.transactionService.resetTransactionsList();
const settings: Settings = new Settings(this.scan);
const readyStateElements: { network: number } = { network: 2 };
settings.w3.provider = environment.web3Provider;
settings.w3.engine = Web3Service.getInstance();
settings.registry = await RegistryService.getRegistry();
settings.w3.engine = this.registryService.getWeb3();
settings.registry = this.registryService.getRegistry();
settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry);
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
window.dispatchEvent(this.newEvent(transaction, 'cic_transfer'));
window.dispatchEvent(this.newTransferEvent(transaction));
};
settings.txHelper.onconversion = async (transaction: any): Promise<any> => {
window.dispatchEvent(this.newEvent(transaction, 'cic_convert'));
window.dispatchEvent(this.newConversionEvent(transaction));
};
// settings.registry.onload = (addressReturned: string): void => {
// this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
// this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
// };
settings.registry.onload = (addressReturned: number): void => {
this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
};
settings.registry.load();
}
readyStateProcessor(
settings: Settings,
bit: number,
address: string,
offset: number,
limit: number
): void {
// tslint:disable-next-line:no-bitwise
readyStateProcessor(settings: Settings, bit: number, address: string, offset: number, limit: number): void {
this.readyState |= bit;
if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js');
@@ -64,39 +54,34 @@ export class BlockSyncService {
w3_provider: settings.w3.provider,
});
if (address === null) {
this.transactionService
.getAllTransactions(offset, limit)
.pipe(first())
.subscribe((res) => {
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
this.fetcher(settings, res);
});
} else {
this.transactionService
.getAddressTransactions(address, offset, limit)
.pipe(first())
.subscribe((res) => {
this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => {
this.fetcher(settings, res);
});
}
}
}
newEvent(tx: any, eventType: string): any {
return new CustomEvent(eventType, {
newTransferEvent(tx: any): any {
return new CustomEvent('cic_transfer', {
detail: {
tx,
},
});
}
async scan(
settings: Settings,
lo: number,
hi: number,
bloomBlockBytes: Uint8Array,
bloomBlocktxBytes: Uint8Array,
bloomRounds: any
): Promise<void> {
newConversionEvent(tx: any): any {
return new CustomEvent('cic_convert', {
detail: {
tx,
},
});
}
async scan(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');
w.onmessage = (m) => {
settings.txHelper.processReceipt(m.data);
@@ -105,7 +90,10 @@ export class BlockSyncService {
w3_provider: settings.w3.provider,
lo,
hi,
filters: [bloomBlockBytes, bloomBlocktxBytes],
filters: [
bloomBlockBytes,
bloomBlocktxBytes,
],
filter_rounds: bloomRounds,
});
}
@@ -113,19 +101,12 @@ export class BlockSyncService {
fetcher(settings: Settings, transactionsInfo: any): void {
const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter);
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 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,
transactionsInfo.low,
transactionsInfo.high,
bOne,
bTwo,
transactionsInfo.filter_rounds
);
settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds);
}
}

View File

@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ElectronService } from './electron.service';
describe('ElectronService', () => {
let service: ElectronService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ElectronService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -1,65 +0,0 @@
import { Injectable } from '@angular/core';
import { IpcRenderer } from 'electron';
import {BehaviorSubject} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ElectronService {
private ipc: IpcRenderer;
images = new BehaviorSubject<string[]>([]);
directory = new BehaviorSubject<string[]>([]);
constructor() {
if ((window as any).require) {
console.log('Pass');
try {
console.log('supported');
this.ipc = (window as any).require('electron').ipcRenderer;
} catch (error) {
console.error(error);
throw error;
}
} else {
console.warn('Could not load electron ipc');
}
}
testIpc(): void {
this.ipc.on('my-custom-signal', (event, arg) => {
console.log('Received acknowledged from backend about receipt of our signal.');
console.log(event);
console.log(arg);
});
this.ipc.on('getImageResponse', (event, images) => {
this.images.next(images);
});
this.ipc.on('getDirectoryResponse', (event, directory) => {
this.directory.next(directory);
});
console.log('Sending message to backend.');
this.ipc.send('my-custom-signal', 'hello, are you there?');
}
navigateDirectory(path): any {
this.ipc.send('navigateDirectory', path);
}
async getFiles(): Promise<string[]> {
console.log('Starting');
return new Promise<string[]>((resolve, reject) => {
this.ipc.once('getFilesResponse', (event, arg) => {
resolve(arg);
});
this.ipc.send('getFiles');
});
}
openModal(): void {
console.log('Open a modal.');
this.ipc.send('openModal');
}
}

View File

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

View File

@@ -6,4 +6,3 @@ export * from '@app/_services/block-sync.service';
export * from '@app/_services/location.service';
export * from '@app/_services/logging.service';
export * from '@app/_services/error-dialog.service';
export * from '@app/_services/web3.service';

View File

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

View File

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

View File

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

View File

@@ -1,76 +1,41 @@
import { Injectable } from '@angular/core';
import { EventEmitter, Injectable } from '@angular/core';
import {environment} from '@src/environments/environment';
import {BehaviorSubject, Observable} from 'rxjs';
import {CICRegistry} from 'cic-client';
import {TokenRegistry} from '@app/_eth';
import {HttpClient} from '@angular/common/http';
import {RegistryService} from '@app/_services/registry.service';
import { Token } from '@app/_models';
import {BehaviorSubject, Observable, Subject} from 'rxjs';
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class TokenService {
registry: CICRegistry;
tokenRegistry: TokenRegistry;
onload: (status: boolean) => void;
tokens: Array<Token> = [];
private tokensList: BehaviorSubject<Array<Token>> = new BehaviorSubject<Array<Token>>(this.tokens);
tokensSubject: Observable<Array<Token>> = this.tokensList.asObservable();
LoadEvent: EventEmitter<number> = new EventEmitter<number>();
constructor(private httpClient: HttpClient) {}
async init(): Promise<void> {
this.registry = await RegistryService.getRegistry();
constructor(
private httpClient: HttpClient,
private registryService: RegistryService,
) {
this.registry = registryService.getRegistry();
this.registry.load();
this.registry.onload = async (address: string): Promise<void> => {
this.tokenRegistry = new TokenRegistry(
await this.registry.getContractAddressByName('TokenRegistry')
);
this.onload(this.tokenRegistry !== undefined);
this.tokenRegistry = new TokenRegistry(await this.registry.getContractAddressByName('TokenRegistry'));
this.LoadEvent.next(Date.now());
};
}
addToken(token: Token): void {
const savedIndex = this.tokens.findIndex((tk) => tk.address === token.address);
if (savedIndex === 0) {
return;
}
if (savedIndex > 0) {
this.tokens.splice(savedIndex, 1);
}
this.tokens.unshift(token);
this.tokensList.next(this.tokens);
}
async getTokens(): Promise<void> {
async getTokens(): Promise<Array<Promise<string>>> {
const count: number = await this.tokenRegistry.totalTokens();
for (let i = 0; i < count; i++) {
const token: Token = await this.getTokenByAddress(await this.tokenRegistry.entry(i));
this.addToken(token);
}
return Array.from({length: count}, async (v, i) => await this.tokenRegistry.entry(i));
}
async getTokenByAddress(address: string): Promise<Token> {
const token: any = {};
const tokenContract = await this.registry.addToken(address);
token.address = address;
token.name = await tokenContract.methods.name().call();
token.symbol = await tokenContract.methods.symbol().call();
token.supply = await tokenContract.methods.totalSupply().call();
token.decimals = await tokenContract.methods.decimals().call();
return token;
getTokenBySymbol(symbol: string): Observable<any> {
return this.httpClient.get(`${environment.cicCacheUrl}/tokens/${symbol}`);
}
async getTokenBySymbol(symbol: string): Promise<Observable<Token>> {
const tokenSubject: Subject<Token> = new Subject<Token>();
await this.getTokens();
this.tokensSubject.subscribe((tokens) => {
const queriedToken = tokens.find((token) => token.symbol === symbol);
tokenSubject.next(queriedToken);
});
return tokenSubject.asObservable();
}
async getTokenBalance(address: string): Promise<(address: string) => Promise<number>> {
async getTokenBalance(address: string): Promise<number> {
const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0));
return await sarafuToken.methods.balanceOf(address).call();
}

View File

@@ -11,7 +11,7 @@ describe('TransactionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
imports: [HttpClientTestingModule]
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);

View File

@@ -17,11 +17,10 @@ import { HttpClient } from '@angular/common/http';
import {CICRegistry} from 'cic-client';
import {RegistryService} from '@app/_services/registry.service';
import Web3 from 'web3';
import { Web3Service } from '@app/_services/web3.service';
const vCard = require('vcard-parser');
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class TransactionService {
transactions: any[] = [];
@@ -35,15 +34,12 @@ export class TransactionService {
private httpClient: HttpClient,
private authService: AuthService,
private userService: UserService,
private loggingService: LoggingService
private loggingService: LoggingService,
private registryService: RegistryService,
) {
this.web3 = Web3Service.getInstance();
}
async init(): Promise<void> {
await this.authService.init();
await this.userService.init();
this.registry = await RegistryService.getRegistry();
this.web3 = this.registryService.getWeb3();
this.registry = registryService.getRegistry();
this.registry.load();
}
getAllTransactions(offset: number, limit: number): Observable<any> {
@@ -51,81 +47,49 @@ export class TransactionService {
}
getAddressTransactions(address: string, offset: number, limit: number): Observable<any> {
return this.httpClient.get(`${environment.cicCacheUrl}/tx/user/${address}/${offset}/${limit}`);
return this.httpClient.get(`${environment.cicCacheUrl}/tx/${address}/${offset}/${limit}`);
}
async setTransaction(transaction, cacheSize: number): Promise<void> {
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === transaction.tx.txHash)) {
return;
}
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash)) { return; }
transaction.value = Number(transaction.value);
transaction.type = 'transaction';
try {
this.userService
.getAccountDetailsFromMeta(await User.toKey(transaction.from))
.pipe(first())
.subscribe(
(res) => {
transaction.sender = this.getAccountInfo(res, cacheSize);
},
(error) => {
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.from)).pipe(first()).subscribe((res) => {
transaction.sender = this.getAccountInfo(res.body);
}, error => {
transaction.sender = defaultAccount;
this.userService.addAccount(defaultAccount, cacheSize);
}
);
this.userService
.getAccountDetailsFromMeta(await User.toKey(transaction.to))
.pipe(first())
.subscribe(
(res) => {
transaction.recipient = this.getAccountInfo(res, cacheSize);
},
(error) => {
});
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe((res) => {
transaction.recipient = this.getAccountInfo(res.body);
}, error => {
transaction.recipient = defaultAccount;
this.userService.addAccount(defaultAccount, cacheSize);
}
);
});
} finally {
this.addTransaction(transaction, cacheSize);
}
}
async setConversion(conversion, cacheSize): Promise<void> {
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === conversion.tx.txHash)) {
return;
}
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === conversion.tx.txHash)) { return; }
conversion.type = 'conversion';
conversion.fromValue = Number(conversion.fromValue);
conversion.toValue = Number(conversion.toValue);
try {
this.userService
.getAccountDetailsFromMeta(await User.toKey(conversion.trader))
.pipe(first())
.subscribe(
(res) => {
conversion.sender = conversion.recipient = this.getAccountInfo(res);
},
(error) => {
this.userService.getAccountDetailsFromMeta(await User.toKey(conversion.trader)).pipe(first()).subscribe((res) => {
conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
}, error => {
conversion.sender = conversion.recipient = defaultAccount;
this.userService.addAccount(defaultAccount, cacheSize);
}
);
});
} finally {
this.addTransaction(conversion, cacheSize);
}
}
addTransaction(transaction, cacheSize: number): void {
const savedIndex = this.transactions.findIndex((tx) => tx.tx.txHash === transaction.tx.txHash);
if (savedIndex === 0) {
return;
}
if (savedIndex > 0) {
this.transactions.splice(savedIndex, 1);
}
this.transactions.unshift(transaction);
if (this.transactions.length > cacheSize) {
this.transactions.length = Math.min(this.transactions.length, cacheSize);
this.transactions.length = cacheSize;
}
this.transactionList.next(this.transactions);
}
@@ -135,32 +99,20 @@ export class TransactionService {
this.transactionList.next(this.transactions);
}
getAccountInfo(account: string, cacheSize: number = 100): any {
const accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
getAccountInfo(account: string): any {
let accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
this.userService.addAccount(accountInfo, cacheSize);
return accountInfo;
}
async transferRequest(
tokenAddress: string,
senderAddress: string,
recipientAddress: string,
value: number
): Promise<any> {
this.registry.onload = async (addressReturned: string): Promise<void> => {
const transferAuthAddress = await this.registry.getContractAddressByName(
'TransferAuthorization'
);
async transferRequest(tokenAddress: string, senderAddress: string, recipientAddress: string, value: number): Promise<any> {
const transferAuthAddress = await this.registry.getContractAddressByName('TransferAuthorization');
const hashFunction = new Keccak(256);
hashFunction.update('createRequest(address,address,address,uint256)');
const hash = hashFunction.digest();
const methodSignature = hash.toString('hex').substring(0, 8);
const abiCoder = new utils.AbiCoder();
const abi = await abiCoder.encode(
['address', 'address', 'address', 'uint256'],
[senderAddress, recipientAddress, tokenAddress, value]
);
const abi = await abiCoder.encode(['address', 'address', 'address', 'uint256'], [senderAddress, recipientAddress, tokenAddress, value]);
const data = fromHex(methodSignature + strip0x(abi));
const tx = new Tx(environment.bloxbergChainId);
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);
@@ -185,6 +137,5 @@ export class TransactionService {
this.loggingService.sendInfoLevelMessage(`Result: ${result}`);
const transaction = await this.web3.eth.getTransaction(result.transactionHash);
this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`);
};
}
}

View File

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

View File

@@ -1,5 +1,5 @@
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 {environment} from '@src/environments/environment';
import {first} from 'rxjs/operators';
@@ -17,7 +17,7 @@ import { add0x } from '@src/assets/js/ethtx/dist/hex';
const vCard = require('vcard-parser');
@Injectable({
providedIn: 'root',
providedIn: 'root'
})
export class UserService {
headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'});
@@ -26,9 +26,7 @@ export class UserService {
registry: CICRegistry;
accounts: Array<AccountDetails> = [];
private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<
Array<AccountDetails>
>(this.accounts);
private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<Array<AccountDetails>>(this.accounts);
accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable();
actions: Array<any> = [];
@@ -39,20 +37,38 @@ export class UserService {
private httpClient: HttpClient,
private loggingService: LoggingService,
private tokenService: TokenService,
private registryService: RegistryService,
private authService: AuthService
) {}
) {
//this.authService.init().then(() => {
// this.keystore = authService.mutableKeyStore;
// this.signer = new PGPSigner(this.keystore);
//});
//this.registry = registryService.getRegistry();
//this.registry.load();
}
async init(): Promise<void> {
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.tokenService.init();
// await this.registryService.load();
// TODO key store is defined
this.keystore = this.authService.mutableKeyStore;
this.signer = new PGPSigner(this.keystore);
this.registry = await RegistryService.getRegistry();
this.registry = this.registryService.getRegistry();
return of(0);
} catch (error) {
console.log('ERROR: Failed to initiialize User Service', error)
return throwError(error);
}
}
resetPin(phone: string): Observable<any> {
const params: HttpParams = new HttpParams().set('phoneNumber', phone);
return this.httpClient.put(`${environment.cicUssdUrl}/pin`, { params });
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
}
getAccountStatus(phone: string): Observable<any> {
@@ -64,26 +80,16 @@ export class UserService {
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
}
async changeAccountInfo(
address: string,
name: string,
phoneNumber: string,
age: string,
type: string,
bio: string,
gender: string,
businessCategory: string,
userLocation: string,
location: string,
locationType: string
async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string,
businessCategory: string, userLocation: string, location: string, locationType: string
): Promise<any> {
const accountInfo: any = {
vcard: {
fn: [{}],
n: [{}],
tel: [{}],
tel: [{}]
},
location: {},
location: {}
};
accountInfo.vcard.fn[0].value = name;
accountInfo.vcard.n[0].value = name.split(' ');
@@ -99,52 +105,33 @@ export class UserService {
await vcardValidation(accountInfo.vcard);
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
const accountKey: string = await User.toKey(address);
this.getAccountDetailsFromMeta(accountKey)
.pipe(first())
.subscribe(
async (res) => {
this.getAccountDetailsFromMeta(accountKey).pipe(first()).subscribe(async res => {
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const update: Array<ArgPair> = [];
for (const prop of Object.keys(accountInfo)) {
for (const prop in accountInfo) {
update.push(new ArgPair(prop, accountInfo[prop]));
}
syncableAccount.update(update, 'client-branch');
await personValidation(syncableAccount.m.data);
await this.updateMeta(syncableAccount, accountKey, this.headers);
},
async (error) => {
this.loggingService.sendErrorLevelMessage(
'Cannot find account info in meta service',
this,
{ error }
);
}, async error => {
this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error});
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
await this.updateMeta(syncableAccount, accountKey, this.headers);
}
);
});
return accountKey;
}
async updateMeta(
syncableAccount: Syncable,
accountKey: string,
headers: HttpHeaders
): Promise<any> {
async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> {
const envelope: Envelope = await this.wrap(syncableAccount , this.signer);
const reqBody: string = envelope.toJSON();
this.httpClient
.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
.pipe(first())
.subscribe((res) => {
this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
});
}
getActions(): void {
this.httpClient
.get(`${environment.cicCacheUrl}/actions`)
.pipe(first())
.subscribe((res) => this.actionsList.next(res));
this.httpClient.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res));
}
getActionById(id: string): Observable<any> {
@@ -179,53 +166,48 @@ export class UserService {
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
this.resetAccountsList();
const accountIndexAddress: string = await this.registry.getContractAddressByName(
'AccountRegistry'
);
let accountAddresses: Array<string>;
try {
const accountIndexAddress: string = await this.registry.getContractAddressByName('AccountRegistry');
const accountIndexQuery = new AccountIndex(accountIndexAddress);
const accountAddresses: Array<string> = await accountIndexQuery.last(limit);
const totalAccounts = await accountIndexQuery.totalAccounts()
const accountAddresses = await accountIndexQuery.last(totalAccounts);
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)) {
await this.getAccountByAddress(accountAddress, limit);
}
}
async getAccountByAddress(
accountAddress: string,
limit: number = 100
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
.pipe(first())
.subscribe(async (res) => {
async getAccountByAddress(accountAddress: string, limit: number = 100): Promise<Observable<AccountDetails>> {
let 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 accountInfo = account.m.data;
await personValidation(accountInfo);
this.tokenService.onload = async (status: boolean): Promise<void> => {
accountInfo.balance = await this.tokenService.getTokenBalance(
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
);
};
accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]);
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
await vcardValidation(accountInfo.vcard);
this.addAccount(accountInfo, limit);
this.accounts.unshift(accountInfo);
if (this.accounts.length > limit) {
this.accounts.length = limit;
}
this.accountsList.next(this.accounts);
accountSubject.next(accountInfo);
});
return accountSubject.asObservable();
}
async getAccountByPhone(
phoneNumber: string,
limit: number = 100
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber))
.pipe(first())
.subscribe(async (res) => {
async getAccountByPhone(phoneNumber: string, limit: number = 100): Promise<Observable<AccountDetails>> {
let 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 address: string = response.m.data;
const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit);
account.subscribe((result) => {
account.subscribe(result => {
accountSubject.next(result);
});
});
@@ -237,9 +219,7 @@ export class UserService {
this.accountsList.next(this.accounts);
}
searchAccountByName(name: string): any {
return;
}
searchAccountByName(name: string): any { return; }
getCategories(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/categories`);
@@ -260,23 +240,6 @@ export class UserService {
getGenders(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/genders`);
}
}
addAccount(account: AccountDetails, cacheSize: number): void {
const savedIndex = this.accounts.findIndex(
(acc) =>
acc.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0] ===
account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
);
if (savedIndex === 0) {
return;
}
if (savedIndex > 0) {
this.accounts.splice(savedIndex, 1);
}
this.accounts.unshift(account);
if (this.accounts.length > cacheSize) {
this.accounts.length = Math.min(this.accounts.length, cacheSize);
}
this.accountsList.next(this.accounts);
}
}

View File

@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { Web3Service } from './web3.service';
describe('Web3Service', () => {
let service: Web3Service;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(Web3Service);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@@ -1,19 +0,0 @@
import { Injectable } from '@angular/core';
import Web3 from 'web3';
import { environment } from '@src/environments/environment';
@Injectable({
providedIn: 'root',
})
export class Web3Service {
private static web3: Web3;
constructor() {}
public static getInstance(): Web3 {
if (!Web3Service.web3) {
Web3Service.web3 = new Web3(environment.web3Provider);
}
return Web3Service.web3;
}
}

View File

@@ -3,22 +3,15 @@ import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import {AuthGuard} from '@app/_guards';
const routes: Routes = [
{ 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: '**', redirectTo: '', pathMatch: 'full' },
{ 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: '**', redirectTo: '', pathMatch: 'full' }
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
useHash: true,
}),
],
exports: [RouterModule],
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,6 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
exports: [RouterModule]
})
export class AuthRoutingModule { }

View File

@@ -1,9 +1,8 @@
<app-network-status></app-network-status>
<div class="container">
<div class="row justify-content-center mt-5 mb-5">
<div class="col-lg-6 col-md-8 col-sm-10">
<div class="card">
<mat-card-title class="card-header pt-4 pb-4 text-center background-dark">
<mat-card-title class="card-header pt-4 pb-4 text-center bg-dark">
<a routerLink="/">
<h1 class="text-white">CICADA</h1>
</a>

View File

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

View File

@@ -8,7 +8,7 @@ import { Router } from '@angular/router';
selector: 'app-auth',
templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AuthComponent implements OnInit {
keyForm: FormGroup;
@@ -33,16 +33,12 @@ export class AuthComponent implements OnInit {
// }
}
get keyFormStub(): any {
return this.keyForm.controls;
}
get keyFormStub(): any { return this.keyForm.controls; }
async onSubmit(): Promise<void> {
this.submitted = true;
if (this.keyForm.invalid) {
return;
}
if (this.keyForm.invalid) { return; }
this.loading = true;
await this.authService.setKey(this.keyFormStub.key.value);

View File

@@ -10,7 +10,7 @@ import { MatSelectModule } from '@angular/material/select';
import {MatInputModule} from '@angular/material/input';
import {MatButtonModule} from '@angular/material/button';
import {MatRippleModule} from '@angular/material/core';
import { SharedModule } from '@app/shared/shared.module';
@NgModule({
declarations: [AuthComponent, PasswordToggleDirective],
@@ -23,7 +23,6 @@ import { SharedModule } from '@app/shared/shared.module';
MatInputModule,
MatButtonModule,
MatRippleModule,
SharedModule,
],
]
})
export class AuthModule { }

View File

@@ -34,7 +34,7 @@
<strong> {{account?.vcard?.fn[0].value}} </strong>
</h3>
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | unixDate}}</span>
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | date}}</span>
<span class="ml-2"><strong>Address:</strong>
<a href="{{bloxbergLink}}" target="_blank"> {{accountAddress}} </a>
<img src="assets/images/checklist.svg" class="ml-2" height="20rem" (click)="copyAddress()" alt="Copy">
@@ -48,19 +48,10 @@
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>First Name: *</mat-label>
<input matInput type="text" id="firstName" placeholder="{{account?.vcard?.fn[0].value.split(' ')[0]}}"
value="{{account?.vcard?.fn[0].value.split(' ')[0]}}" formControlName="firstName" [errorStateMatcher]="matcher">
<mat-error *ngIf="submitted && accountInfoFormStub.firstName.errors">First Name is required.</mat-error>
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>Last Name(s): *</mat-label>
<input matInput type="text" id="lastName" placeholder="{{account?.vcard?.fn[0].value.split(' ').slice(1).join(' ')}}"
value="{{account?.vcard?.fn[0].value.split(' ').slice(1).join(' ')}}" formControlName="lastName" [errorStateMatcher]="matcher">
<mat-error *ngIf="submitted && accountInfoFormStub.lastName.errors">Last Name is required.</mat-error>
<mat-label>Name(s): *</mat-label>
<input matInput type="text" id="givenNames" placeholder="{{account?.vcard?.fn[0].value}}"
value="{{account?.vcard?.fn[0].value}}" formControlName="name" [errorStateMatcher]="matcher">
<mat-error *ngIf="submitted && accountInfoFormStub.name.errors">Name is required.</mat-error>
</mat-form-field>
</div>
@@ -219,9 +210,12 @@
<tr>
<td>{{account?.vcard?.fn[0].value}}</td>
<td>{{account?.balance | tokenRatio}}</td>
<td>{{account?.date_registered | unixDate}}</td>
<td>{{account?.date_registered | date}}</td>
<td>
<span class="badge badge-success badge-pill">
<span *ngIf="accountStatus === 'active'" class="badge badge-success badge-pill">
{{accountStatus}}
</span>
<span *ngIf="accountStatus === 'blocked'" class="badge badge-danger badge-pill">
{{accountStatus}}
</span>
</td>
@@ -234,7 +228,7 @@
<mat-tab-group *ngIf="account" dynamicHeight mat-align-tabs="start">
<mat-tab label="Transactions">
<app-transaction-details [transaction]="transaction" (closeWindow)="transaction = $event"></app-transaction-details>
<app-transaction-details [transaction]="transaction"></app-transaction-details>
<div class="card mt-1">
<div class="card-header">
<div class="row">
@@ -258,17 +252,17 @@
<mat-icon matSuffix>search</mat-icon>
</mat-form-field>
<table mat-table class="mat-elevation-z10" [dataSource]="transactionsDataSource" matSort matSortActive="created"
<mat-table class="mat-elevation-z10" [dataSource]="transactionsDataSource" matSort matSortActive="created"
#TransactionTableSort="matSort" matSortDirection="asc" matSortDisableClear>
<ng-container matColumnDef="sender">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sender </th>
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value || transaction.from}} </td>
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value}} </td>
</ng-container>
<ng-container matColumnDef="recipient">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Recipient </th>
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value || transaction.to}} </td>
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value}} </td>
</ng-container>
<ng-container matColumnDef="value">
@@ -281,7 +275,7 @@
<ng-container matColumnDef="created">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Created </th>
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | unixDate}} </td>
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | date}} </td>
</ng-container>
<ng-container matColumnDef="type">
@@ -291,10 +285,10 @@
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="transactionsDisplayedColumns"></tr>
<tr mat-row *matRowDef="let transaction; columns: transactionsDisplayedColumns" matRipple
(click)="viewTransaction(transaction)"></tr>
</table>
<mat-header-row *matHeaderRowDef="transactionsDisplayedColumns"></mat-header-row>
<mat-row *matRowDef="let transaction; columns: transactionsDisplayedColumns" matRipple
(click)="viewTransaction(transaction)"></mat-row>
</mat-table>
<mat-paginator #TransactionTablePaginator="matPaginator" [pageSize]="transactionsDefaultPageSize"
[pageSizeOptions]="transactionsPageSizeOptions" showFirstLastButtons></mat-paginator>
@@ -343,7 +337,7 @@
<ng-container matColumnDef="created">
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user?.date_registered | unixDate}} </mat-cell>
<mat-cell *matCellDef="let user"> {{user?.date_registered | date}} </mat-cell>
</ng-container>
<ng-container matColumnDef="balance">

View File

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

View File

@@ -1,21 +1,8 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
ViewChild,
} from '@angular/core';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
import {MatTableDataSource} from '@angular/material/table';
import {MatPaginator} from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort';
import {
BlockSyncService,
LocationService,
LoggingService,
TokenService,
TransactionService,
UserService,
} from '@app/_services';
import {BlockSyncService, LocationService, 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';
@@ -29,7 +16,7 @@ import { AccountDetails, AreaName, AreaType, Category, Transaction } from '@app/
selector: 'app-account-details',
templateUrl: './account-details.component.html',
styleUrls: ['./account-details.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccountDetailsComponent implements OnInit {
transactionsDataSource: MatTableDataSource<any>;
@@ -76,19 +63,10 @@ export class AccountDetailsComponent implements OnInit {
private loggingService: LoggingService,
private blockSyncService: BlockSyncService,
private cdr: ChangeDetectorRef,
private snackBar: MatSnackBar
private snackBar: MatSnackBar,
) {
this.route.paramMap.subscribe((params: Params) => {
this.accountAddress = add0x(params.get('id'));
this.bloxbergLink =
'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
});
}
async ngOnInit(): Promise<void> {
this.accountInfoForm = this.formBuilder.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required],
name: ['', Validators.required],
phoneNumber: ['', Validators.required],
age: ['', Validators.required],
type: ['', Validators.required],
@@ -99,96 +77,56 @@ export class AccountDetailsComponent implements OnInit {
location: ['', Validators.required],
locationType: ['', Validators.required],
});
await this.blockSyncService.init();
await this.tokenService.init();
await this.transactionService.init();
await this.userService.init();
await this.blockSyncService.blockSync(this.accountAddress);
this.userService.resetAccountsList();
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
async (res) => {
this.route.paramMap.subscribe(async (params: Params) => {
this.accountAddress = add0x(params.get('id'));
this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(async res => {
if (res !== undefined) {
this.account = res;
this.cdr.detectChanges();
this.loggingService.sendInfoLevelMessage(this.account);
const fullName = this.account.vcard?.fn[0].value.split(' ');
// this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first())
// .subscribe(response => this.accountStatus = response);
this.accountInfoForm.patchValue({
firstName: fullName[0],
lastName: fullName.slice(1).join(' '),
name: this.account.vcard?.fn[0].value,
phoneNumber: this.account.vcard?.tel[0].value,
age: this.account.age,
type: this.account.type,
bio: this.account.products,
gender: this.account.gender,
businessCategory:
this.account.category ||
this.userService.getCategoryByProduct(this.account.products[0]),
businessCategory: this.account.category,
userLocation: this.account.location.area_name,
location:
this.account.location.area ||
this.locationService
.getAreaNameByLocation(this.account.location.area_name)
.pipe(first())
.subscribe((response) => {
return response;
}),
locationType:
this.account.location.area_type ||
this.locationService
.getAreaTypeByArea(this.accountInfoFormStub.location.value)
.pipe(first())
.subscribe((response) => {
return response;
}),
location: this.account.location.area,
locationType: this.account.location.area_type,
});
this.userService
.getAccountStatus(this.account.vcard?.tel[0].value)
.pipe(first())
.subscribe((response) => (this.accountStatus = response.status));
} else {
alert('Account not found!');
}
});
this.blockSyncService.blockSync(this.accountAddress);
});
this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res);
this.locationService.getAreaNames().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);
}
);
this.userService.accountsSubject.subscribe((accounts) => {
ngOnInit(): void {
this.userService.accountsSubject.subscribe(accounts => {
this.userDataSource = new MatTableDataSource<any>(accounts);
this.userDataSource.paginator = this.userTablePaginator;
this.userDataSource.sort = this.userTableSort;
this.accounts = accounts;
this.cdr.detectChanges();
});
this.transactionService.transactionsSubject.subscribe((transactions) => {
this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionsDataSource = new MatTableDataSource<any>(transactions);
this.transactionsDataSource.paginator = this.transactionTablePaginator;
this.transactionsDataSource.sort = this.transactionTableSort;
this.transactions = transactions;
this.cdr.detectChanges();
});
this.userService
.getCategories()
.pipe(first())
.subscribe((res) => (this.categories = res));
this.locationService
.getAreaNames()
.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));
}
doTransactionFilter(value: string): void {
@@ -204,23 +142,17 @@ export class AccountDetailsComponent implements OnInit {
}
viewAccount(account): void {
this.router.navigateByUrl(
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
}
get accountInfoFormStub(): any {
return this.accountInfoForm.controls;
}
get accountInfoFormStub(): any { return this.accountInfoForm.controls; }
async saveInfo(): Promise<void> {
this.submitted = true;
if (this.accountInfoForm.invalid || !confirm(`Change user's profile information?`)) {
return;
}
if (this.accountInfoForm.invalid || !confirm('Change user\'s profile information?')) { return; }
const accountKey = await this.userService.changeAccountInfo(
this.accountAddress,
this.accountInfoFormStub.firstName.value + ' ' + this.accountInfoFormStub.lastName.value,
this.accountInfoFormStub.name.value,
this.accountInfoFormStub.phoneNumber.value,
this.accountInfoFormStub.age.value,
this.accountInfoFormStub.type.value,
@@ -236,38 +168,29 @@ export class AccountDetailsComponent implements OnInit {
filterAccounts(): void {
if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe((accounts) => {
this.userService.accountsSubject.subscribe(accounts => {
this.userDataSource.data = accounts;
this.accounts = accounts;
});
} else {
this.userDataSource.data = this.accounts.filter(
(account) => account.type === this.accountsType
);
this.userDataSource.data = this.accounts.filter(account => account.type === this.accountsType);
}
}
filterTransactions(): void {
if (this.transactionsType === 'all') {
this.transactionService.transactionsSubject.subscribe((transactions) => {
this.transactionService.transactionsSubject.subscribe(transactions => {
this.transactionsDataSource.data = transactions;
this.transactions = transactions;
});
} else {
this.transactionsDataSource.data = this.transactions.filter(
(transaction) => transaction.type === this.transactionsType
);
this.transactionsDataSource.data = this.transactions.filter(transaction => transaction.type === this.transactionsType);
}
}
resetPin(): void {
if (!confirm(`Reset user's pin?`)) {
return;
}
this.userService
.resetPin(this.account.vcard.tel[0].value)
.pipe(first())
.subscribe((res) => {
if (!confirm('Reset user\'s pin?')) { return; }
this.userService.resetPin(this.account.vcard.tel[0].value).pipe(first()).subscribe(res => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
});
}
@@ -278,9 +201,7 @@ export class AccountDetailsComponent implements OnInit {
copyAddress(): void {
if (copyToClipboard(this.accountAddress)) {
this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', {
duration: 3000,
});
this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', { duration: 3000 });
}
}
}

View File

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

View File

@@ -10,7 +10,7 @@ import { environment } from '@src/environments/environment';
selector: 'app-account-search',
templateUrl: './account-search.component.html',
styleUrls: ['./account-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccountSearchComponent implements OnInit {
nameSearchForm: FormGroup;
@@ -27,11 +27,10 @@ export class AccountSearchComponent implements OnInit {
constructor(
private formBuilder: FormBuilder,
private userService: UserService,
private router: Router
private router: Router,
) { }
async ngOnInit(): Promise<void> {
await this.userService.init();
ngOnInit(): void {
this.nameSearchForm = this.formBuilder.group({
name: ['', Validators.required],
});
@@ -43,21 +42,13 @@ export class AccountSearchComponent implements OnInit {
});
}
get nameSearchFormStub(): any {
return this.nameSearchForm.controls;
}
get phoneSearchFormStub(): any {
return this.phoneSearchForm.controls;
}
get addressSearchFormStub(): any {
return this.addressSearchForm.controls;
}
get nameSearchFormStub(): any { return this.nameSearchForm.controls; }
get phoneSearchFormStub(): any { return this.phoneSearchForm.controls; }
get addressSearchFormStub(): any { return this.addressSearchForm.controls; }
onNameSearch(): void {
this.nameSearchSubmitted = true;
if (this.nameSearchForm.invalid) {
return;
}
if (this.nameSearchForm.invalid) { return; }
this.nameSearchLoading = true;
this.userService.searchAccountByName(this.nameSearchFormStub.name.value);
this.nameSearchLoading = false;
@@ -65,17 +56,11 @@ export class AccountSearchComponent implements OnInit {
async onPhoneSearch(): Promise<void> {
this.phoneSearchSubmitted = true;
if (this.phoneSearchForm.invalid) {
return;
}
if (this.phoneSearchForm.invalid) { return; }
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) {
await this.router.navigateByUrl(
`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
} else {
alert('Account not found!');
}
@@ -85,17 +70,11 @@ export class AccountSearchComponent implements OnInit {
async onAddressSearch(): Promise<void> {
this.addressSearchSubmitted = true;
if (this.addressSearchForm.invalid) {
return;
}
if (this.addressSearchForm.invalid) { return; }
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) {
await this.router.navigateByUrl(
`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
await this.router.navigateByUrl(`/accounts/${strip0x(res.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
} else {
alert('Account not found!');
}

View File

@@ -11,11 +11,11 @@ const routes: Routes = [
{ path: 'search', component: AccountSearchComponent },
// { path: 'create', component: CreateAccountComponent },
{ path: ':id', component: AccountDetailsComponent },
{ path: '**', redirectTo: '', pathMatch: 'full' },
{ path: '**', redirectTo: '', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
exports: [RouterModule]
})
export class AccountsRoutingModule { }

View File

@@ -56,7 +56,7 @@
<ng-container matColumnDef="created">
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user?.date_registered | unixDate}} </mat-cell>
<mat-cell *matCellDef="let user"> {{user?.date_registered | date}} </mat-cell>
</ng-container>
<ng-container matColumnDef="balance">

View File

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

View File

@@ -9,12 +9,14 @@ import { strip0x } from '@src/assets/js/ethtx/dist/hex';
import {first} from 'rxjs/operators';
import {environment} from '@src/environments/environment';
import {AccountDetails} from '@app/_models';
import {Observable, of} from 'rxjs';
@Component({
selector: 'app-accounts',
templateUrl: './accounts.component.html',
styleUrls: ['./accounts.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccountsComponent implements OnInit {
dataSource: MatTableDataSource<any>;
@@ -30,28 +32,22 @@ export class AccountsComponent implements OnInit {
constructor(
private userService: UserService,
private loggingService: LoggingService,
private router: Router
) {}
async ngOnInit(): Promise<void> {
await this.userService.init();
try {
// 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 });
private router: Router,
private loggingService: LoggingService
)
{
this.userService = userService;
}
this.userService.accountsSubject.subscribe((accounts) => {
ngOnInit(): void {
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.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.accounts = accounts;
});
this.userService
.getAccountTypes()
.pipe(first())
.subscribe((res) => (this.accountTypes = res));
}
doFilter(value: string): void {
@@ -59,19 +55,17 @@ export class AccountsComponent implements OnInit {
}
async viewAccount(account): Promise<void> {
await this.router.navigateByUrl(
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
await this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`);
}
filterAccounts(): void {
if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe((accounts) => {
this.userService.accountsSubject.subscribe(accounts => {
this.dataSource.data = accounts;
this.accounts = accounts;
});
} 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

@@ -5,6 +5,7 @@ import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module';
import { AccountsComponent } from '@pages/accounts/accounts.component';
import {SharedModule} from '@app/shared/shared.module';
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 {MatTableModule} from '@angular/material/table';
import {MatSortModule} from '@angular/material/sort';
@@ -24,17 +25,19 @@ import { ReactiveFormsModule } from '@angular/forms';
import { AccountSearchComponent } from './account-search/account-search.component';
import {MatSnackBarModule} from '@angular/material/snack-bar';
@NgModule({
declarations: [
AccountsComponent,
AccountDetailsComponent,
CreateAccountComponent,
AccountSearchComponent,
AccountSearchComponent
],
imports: [
CommonModule,
AccountsRoutingModule,
SharedModule,
DataTablesModule,
MatTableModule,
MatSortModule,
MatCheckboxModule,
@@ -51,6 +54,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
MatProgressSpinnerModule,
ReactiveFormsModule,
MatSnackBarModule,
],
]
})
export class AccountsModule { }

View File

@@ -5,6 +5,7 @@ import { AccountsModule } from '@pages/accounts/accounts.module';
import {AppModule} from '@app/app.module';
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
describe('CreateAccountComponent', () => {
let component: CreateAccountComponent;
let fixture: ComponentFixture<CreateAccountComponent>;
@@ -15,10 +16,14 @@ describe('CreateAccountComponent', () => {
CreateAccountComponent,
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
TopbarStubComponent
],
imports: [AccountsModule, AppModule],
}).compileComponents();
imports: [
AccountsModule,
AppModule
]
})
.compileComponents();
});
beforeEach(() => {

View File

@@ -9,7 +9,7 @@ import { AreaName, Category } from '@app/_models';
selector: 'app-create-account',
templateUrl: './create-account.component.html',
styleUrls: ['./create-account.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CreateAccountComponent implements OnInit {
createForm: FormGroup;
@@ -26,8 +26,7 @@ export class CreateAccountComponent implements OnInit {
private userService: UserService
) { }
async ngOnInit(): Promise<void> {
await this.userService.init();
ngOnInit(): void {
this.createForm = this.formBuilder.group({
accountType: ['', Validators.required],
idNumber: ['', Validators.required],
@@ -38,35 +37,19 @@ export class CreateAccountComponent implements OnInit {
location: ['', Validators.required],
gender: ['', Validators.required],
referrer: ['', Validators.required],
businessCategory: ['', Validators.required],
businessCategory: ['', Validators.required]
});
this.userService
.getCategories()
.pipe(first())
.subscribe((res) => (this.categories = 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));
this.userService.getCategories().pipe(first()).subscribe(res => this.categories = 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 {
return this.createForm.controls;
}
get createFormStub(): any { return this.createForm.controls; }
onSubmit(): void {
this.submitted = true;
if (this.createForm.invalid || !confirm('Create account?')) {
return;
}
if (this.createForm.invalid || !confirm('Create account?')) { return; }
this.submitted = false;
}
}

View File

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

View File

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

View File

@@ -18,8 +18,8 @@ import { Action } from '../../_models';
state('collapsed', style({height: '0px', minHeight: 0, visibility: 'hidden'})),
state('expanded', style({height: '*', visibility: 'visible'})),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]),
],
])
]
})
export class AdminComponent implements OnInit {
dataSource: MatTableDataSource<any>;
@@ -30,12 +30,12 @@ export class AdminComponent implements OnInit {
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private userService: UserService, private loggingService: LoggingService) {}
async ngOnInit(): Promise<void> {
await this.userService.init();
constructor(
private userService: UserService,
private loggingService: LoggingService
) {
this.userService.getActions();
this.userService.actionsSubject.subscribe((actions) => {
this.userService.actionsSubject.subscribe(actions => {
this.dataSource = new MatTableDataSource<any>(actions);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
@@ -43,6 +43,9 @@ export class AdminComponent implements OnInit {
});
}
ngOnInit(): void {
}
doFilter(value: string): void {
this.dataSource.filter = value.trim().toLocaleLowerCase();
}
@@ -52,24 +55,14 @@ export class AdminComponent implements OnInit {
}
approveAction(action: any): void {
if (!confirm('Approve action?')) {
return;
}
this.userService
.approveAction(action.id)
.pipe(first())
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
if (!confirm('Approve action?')) { return; }
this.userService.approveAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
this.userService.getActions();
}
disapproveAction(action: any): void {
if (!confirm('Disapprove action?')) {
return;
}
this.userService
.revokeAction(action.id)
.pipe(first())
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
if (!confirm('Disapprove action?')) { return; }
this.userService.revokeAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
this.userService.getActions();
}

View File

@@ -14,6 +14,7 @@ import { MatPaginatorModule } from '@angular/material/paginator';
import {MatButtonModule} from '@angular/material/button';
import {MatRippleModule} from '@angular/material/core';
@NgModule({
declarations: [AdminComponent],
imports: [
@@ -28,7 +29,7 @@ import { MatRippleModule } from '@angular/material/core';
MatSortModule,
MatPaginatorModule,
MatButtonModule,
MatRippleModule,
],
MatRippleModule
]
})
export class AdminModule { }

View File

@@ -5,32 +5,16 @@ import { PagesComponent } from './pages.component';
const routes: Routes = [
{ path: 'home', component: PagesComponent },
{
path: 'tx',
loadChildren: () =>
import('@pages/transactions/transactions.module').then((m) => m.TransactionsModule),
},
{
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' },
{ path: 'tx', loadChildren: () => import('@pages/transactions/transactions.module').then(m => m.TransactionsModule) },
{ 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({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
exports: [RouterModule]
})
export class PagesRoutingModule { }

View File

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

View File

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

View File

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

View File

@@ -15,10 +15,14 @@ describe('OrganizationComponent', () => {
OrganizationComponent,
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
TopbarStubComponent
],
imports: [AppModule, SettingsModule],
}).compileComponents();
imports: [
AppModule,
SettingsModule,
]
})
.compileComponents();
});
beforeEach(() => {

View File

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

View File

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

View File

@@ -23,10 +23,9 @@
SETTINGS
</mat-card-title>
<div class="card-body">
<h4>CICADA Admin Credentials</h4>
<span><strong>UserId: </strong> {{ userInfo?.userid }} </span><br>
<span><strong>Username: </strong> {{ userInfo?.name }} </span><br>
<span><strong>Email: </strong> {{ userInfo?.email }} </span>
<h4>Kobo Toolbox Credentials</h4>
<span><strong>Username: </strong> admin_reserve </span><br>
<span><strong>Password: </strong> ******** </span>
</div>
<hr>
<div class="card-body">

View File

@@ -15,10 +15,14 @@ describe('SettingsComponent', () => {
SettingsComponent,
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
TopbarStubComponent
],
imports: [AppModule, SettingsModule],
}).compileComponents();
imports: [
AppModule,
SettingsModule,
]
})
.compileComponents();
});
beforeEach(() => {

View File

@@ -10,29 +10,28 @@ import { exportCsv } from '@app/_helpers';
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SettingsComponent implements OnInit {
date: string;
dataSource: MatTableDataSource<any>;
displayedColumns: Array<string> = ['name', 'email', 'userId'];
trustedUsers: Array<Staff>;
userInfo: Staff;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private authService: AuthService) {}
constructor(
private authService: AuthService
) { }
async ngOnInit(): Promise<void> {
await this.authService.init();
this.authService.trustedUsersSubject.subscribe((users) => {
this.dataSource = new MatTableDataSource<any>(users);
ngOnInit(): void {
const d = new Date();
this.date = `${d.getDate()}/${d.getMonth()}/${d.getFullYear()}`;
this.trustedUsers = this.authService.getTrustedUsers();
this.dataSource = new MatTableDataSource<any>(this.trustedUsers);
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.trustedUsers = users;
});
this.userInfo = this.authService.getPrivateKeyInfo();
}
doFilter(value: string): void {

View File

@@ -19,6 +19,7 @@ import { MatSelectModule } from '@angular/material/select';
import {MatMenuModule} from '@angular/material/menu';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [SettingsComponent, OrganizationComponent],
imports: [
@@ -37,7 +38,7 @@ import { ReactiveFormsModule } from '@angular/forms';
MatCheckboxModule,
MatSelectModule,
MatMenuModule,
ReactiveFormsModule,
],
ReactiveFormsModule
]
})
export class SettingsModule { }

View File

@@ -1,36 +1,60 @@
<div *ngIf="token" class="mb-3 mt-1">
<div class="card text-center">
<!-- Begin page -->
<div class="wrapper">
<app-sidebar></app-sidebar>
<!-- ============================================================== -->
<!-- Start Page Content here -->
<!-- ============================================================== -->
<div id="content">
<app-topbar></app-topbar>
<!-- Start Content-->
<div class="container-fluid text-center" appMenuSelection>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
<li class="breadcrumb-item"><a routerLink="/tokens">Tokens</a></li>
<li class="breadcrumb-item active" aria-current="page">{{token.name}}</li>
</ol>
</nav>
<div class="col-md-6 center-body">
<div class="card">
<mat-card-title class="card-header">
<div class="row">
TOKEN DETAILS
<button mat-raised-button type="button" class="btn btn-outline-secondary ml-auto mr-2" (click)="close()"> CLOSE </button>
</div>
Token
</mat-card-title>
<div class="card-body">
<div>
<span><strong>Name:</strong> {{token?.name}}</span>
<span><strong>Name:</strong> {{token.name}}</span>
</div>
<div>
<span><strong>Symbol:</strong> {{token?.symbol}}</span>
<span><strong>Symbol:</strong> {{token.symbol}}</span>
</div>
<div>
<span><strong>Address:</strong> {{token?.address}}</span>
<span><strong>Address:</strong> {{token.address}}</span>
</div>
<div>
<span><strong>Details:</strong> A community inclusive currency for trading among lower to middle income societies.</span>
</div>
<div>
<span><strong>Supply:</strong> {{token?.supply | tokenRatio}}</span>
<span><strong>Supply:</strong> {{token.supply | tokenRatio}}</span>
</div><br>
<div>
<h2>Reserve</h2>
<div>
<span><strong>Weight:</strong> {{token?.reserveRatio}}</span>
<span><strong>Weight:</strong> {{token.reserveRatio}}</span>
</div>
<div>
<span><strong>Owner:</strong> {{token?.owner}}</span>
<span><strong>Owner:</strong> {{token.owner}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<app-footer appMenuSelection></app-footer>
</div>
<!-- ============================================================== -->
<!-- End Page content -->
<!-- ============================================================== -->
</div>

View File

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

View File

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

View File

@@ -11,6 +11,6 @@ const routes: Routes = [
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
exports: [RouterModule]
})
export class TokensRoutingModule { }

View File

@@ -24,9 +24,6 @@
</div>
</mat-card-title>
<div class="card-body">
<app-token-details [token]="token" (closeWindow)="token = $event"></app-token-details>
<mat-form-field appearance="outline">
<mat-label> Filter </mat-label>
<input matInput type="text" (keyup)="doFilter($event.target.value)" placeholder="Filter">

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