Merge branch 'master' into spencer/censor-pgp-passphrase
# Conflicts: # package-lock.json # src/app/_helpers/http-getter.ts # src/app/_services/auth.service.ts # src/app/auth/auth.component.ts # src/assets/js/hoba-pgp.js # src/styles.scss
This commit is contained in:
commit
159cedc86e
1
.husky/.gitignore
vendored
Normal file
1
.husky/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
_
|
30
.husky/_/husky.sh
Normal file
30
.husky/_/husky.sh
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#!/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
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package.json
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
dist
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"useTabs": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"semi": true,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
32
README.md
32
README.md
@ -10,7 +10,9 @@ Run `npm install -g @angular/cli` to install the angular CLI.
|
|||||||
|
|
||||||
## Development server
|
## Development server
|
||||||
|
|
||||||
Run `npm run start:dev` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
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.
|
||||||
|
|
||||||
## Code scaffolding
|
## Code scaffolding
|
||||||
|
|
||||||
@ -22,11 +24,21 @@ Run `ng generate module module-name --route module-name --module app.module` to
|
|||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Run `npm run build:dev` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `build:prod` script for a production build.
|
Run `ng build` to build the project using local configurations.
|
||||||
|
The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Running unit tests
|
## Running unit tests
|
||||||
|
|
||||||
Run `npm run test:dev` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
## Running end-to-end tests
|
## Running end-to-end tests
|
||||||
|
|
||||||
@ -34,11 +46,19 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac
|
|||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
Environment variables are contained in the `.env` file. See `.env.example` for a template.
|
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.
|
||||||
|
|
||||||
Default environment variables are set in the `set-env.ts` file.
|
Custom environment variables are set via the `set-env.ts` file.
|
||||||
Once loaded they will be populated in the directory `src/environments/`.
|
Once loaded they will be populated in the directory `src/environments/`.
|
||||||
It contains environment variables for development on `environment.ts` and production on `environment.prod.ts`.
|
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.
|
||||||
|
|
||||||
## Further help
|
## Further help
|
||||||
|
|
||||||
|
15
angular.json
15
angular.json
@ -26,19 +26,17 @@
|
|||||||
"aot": true,
|
"aot": true,
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/favicon.ico",
|
"src/favicon.ico",
|
||||||
"src/assets"
|
"src/assets",
|
||||||
|
"src/manifest.webmanifest"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
"node_modules/datatables.net-dt/css/jquery.dataTables.css",
|
|
||||||
"node_modules/bootstrap/dist/css/bootstrap.min.css"
|
"node_modules/bootstrap/dist/css/bootstrap.min.css"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/jquery/dist/jquery.js",
|
"node_modules/jquery/dist/jquery.js",
|
||||||
"node_modules/datatables.net/js/jquery.dataTables.js",
|
"node_modules/bootstrap/dist/js/bootstrap.js"
|
||||||
"node_modules/bootstrap/dist/js/bootstrap.js",
|
|
||||||
"node_modules/block-syncer/dist/worker_ondemand.js"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
@ -68,7 +66,9 @@
|
|||||||
"maximumWarning": "6kb",
|
"maximumWarning": "6kb",
|
||||||
"maximumError": "10kb"
|
"maximumError": "10kb"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"serviceWorker": true,
|
||||||
|
"ngswConfigPath": "ngsw-config.json"
|
||||||
},
|
},
|
||||||
"dev": {
|
"dev": {
|
||||||
"fileReplacements": [
|
"fileReplacements": [
|
||||||
@ -110,7 +110,8 @@
|
|||||||
"codeCoverage": true,
|
"codeCoverage": true,
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/favicon.ico",
|
"src/favicon.ico",
|
||||||
"src/assets"
|
"src/assets",
|
||||||
|
"src/manifest.webmanifest"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
||||||
|
31
ngsw-config.json
Normal file
31
ngsw-config.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$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)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5507
package-lock.json
generated
5507
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@ -8,10 +8,17 @@
|
|||||||
"start:prod": "ng serve --prod",
|
"start:prod": "ng serve --prod",
|
||||||
"build:dev": "ng build -c dev",
|
"build:dev": "ng build -c dev",
|
||||||
"build:prod": "ng build --prod",
|
"build:prod": "ng build --prod",
|
||||||
"test:dev": "ng test",
|
"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",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"postinstall": "node patch-webpack.js"
|
"precommit": "npm run format:fix && npm run lint",
|
||||||
|
"postinstall": "node patch-webpack.js",
|
||||||
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -25,28 +32,20 @@
|
|||||||
"@angular/platform-browser": "~10.2.0",
|
"@angular/platform-browser": "~10.2.0",
|
||||||
"@angular/platform-browser-dynamic": "~10.2.0",
|
"@angular/platform-browser-dynamic": "~10.2.0",
|
||||||
"@angular/router": "~10.2.0",
|
"@angular/router": "~10.2.0",
|
||||||
|
"@angular/service-worker": "~10.2.0",
|
||||||
|
"@cicnet/schemas-data-validator": "*",
|
||||||
"@popperjs/core": "^2.5.4",
|
"@popperjs/core": "^2.5.4",
|
||||||
"angular-datatables": "^9.0.2",
|
|
||||||
"block-syncer": "^0.2.4",
|
|
||||||
"bootstrap": "^4.5.3",
|
"bootstrap": "^4.5.3",
|
||||||
"chart.js": "^2.9.4",
|
"cic-client": "0.1.4",
|
||||||
"cic-client": "^0.1.1",
|
"cic-client-meta": "0.0.7-alpha.6",
|
||||||
"cic-client-meta": "0.0.7-alpha.3",
|
|
||||||
"datatables.net": "^1.10.22",
|
|
||||||
"datatables.net-dt": "^1.10.22",
|
|
||||||
"ethers": "^5.0.31",
|
"ethers": "^5.0.31",
|
||||||
|
"http-server": "^0.12.3",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
"mocha": "^8.2.1",
|
|
||||||
"moolb": "^0.1.0",
|
|
||||||
"ng2-charts": "^2.4.2",
|
|
||||||
"ngx-logger": "^4.2.1",
|
"ngx-logger": "^4.2.1",
|
||||||
"openpgp": "^4.10.10",
|
|
||||||
"popper.js": "^1.16.1",
|
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
"sha3": "^2.1.4",
|
"sha3": "^2.1.4",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"vcard-parser": "^1.0.0",
|
"vcard-parser": "^1.0.0",
|
||||||
"vcards-js": "^2.10.0",
|
|
||||||
"web3": "^1.3.0",
|
"web3": "^1.3.0",
|
||||||
"zone.js": "~0.10.2"
|
"zone.js": "~0.10.2"
|
||||||
},
|
},
|
||||||
@ -61,6 +60,7 @@
|
|||||||
"@types/node": "^12.20.6",
|
"@types/node": "^12.20.6",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
"husky": "^6.0.0",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
"karma": "~5.0.0",
|
"karma": "~5.0.0",
|
||||||
@ -69,11 +69,21 @@
|
|||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-junit-reporter": "^2.0.1",
|
"karma-junit-reporter": "^2.0.1",
|
||||||
|
"prettier": "^2.3.0",
|
||||||
|
"pretty-quick": "^3.1.0",
|
||||||
"protractor": "~7.0.0",
|
"protractor": "~7.0.0",
|
||||||
"secp256k1": "^4.0.2",
|
"secp256k1": "^4.0.2",
|
||||||
"ts-node": "~8.3.0",
|
"ts-node": "~8.3.0",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
|
"tslint-angular": "^3.0.3",
|
||||||
|
"tslint-config-prettier": "^1.18.0",
|
||||||
|
"tslint-jasmine-rules": "^1.6.1",
|
||||||
"typescript": "~4.0.2",
|
"typescript": "~4.0.2",
|
||||||
"yargs": "^13.3.2"
|
"yargs": "^13.3.2"
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "pretty-quick --staged & ng lint"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
set-env.ts
10
set-env.ts
@ -6,7 +6,7 @@ require('dotenv').config();
|
|||||||
const environment = argv.environment;
|
const environment = argv.environment;
|
||||||
const isProduction = environment === 'prod';
|
const isProduction = environment === 'prod';
|
||||||
|
|
||||||
const targetPath = isProduction ? `./src/environments/environment.prod.ts` : `./src/environments/environment.ts`;
|
const targetPath = isProduction ? `./src/environments/environment.prod.ts` : `./src/environments/environment.dev.ts`;
|
||||||
|
|
||||||
const environmentVars = `import {NgxLoggerLevel} from 'ngx-logger';
|
const environmentVars = `import {NgxLoggerLevel} from 'ngx-logger';
|
||||||
|
|
||||||
@ -15,13 +15,13 @@ export const environment = {
|
|||||||
bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996},
|
bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996},
|
||||||
logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'},
|
logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'},
|
||||||
serverLogLevel: ${process.env.SERVER_LOG_LEVEL || 'NgxLoggerLevel.OFF'},
|
serverLogLevel: ${process.env.SERVER_LOG_LEVEL || 'NgxLoggerLevel.OFF'},
|
||||||
loggingUrl: '${process.env.CIC_LOGGING_URL || 'http://localhost:8000'}',
|
loggingUrl: '${process.env.CIC_LOGGING_URL || ''}',
|
||||||
cicMetaUrl: '${process.env.CIC_META_URL || 'https://meta.dev.grassrootseconomics.net'}',
|
cicMetaUrl: '${process.env.CIC_META_URL || 'https://meta.dev.grassrootseconomics.net'}',
|
||||||
publicKeysUrl: '${process.env.CIC_KEYS_URL || 'http://localhost:8000/keys.asc'}',
|
publicKeysUrl: '${process.env.CIC_KEYS_URL || 'https://dev.grassrootseconomics.net/.well-known/publickeys'}',
|
||||||
cicCacheUrl: '${process.env.CIC_CACHE_URL || 'https://cache.dev.grassrootseconomics.net'}',
|
cicCacheUrl: '${process.env.CIC_CACHE_URL || 'https://cache.dev.grassrootseconomics.net'}',
|
||||||
web3Provider: '${process.env.CIC_WEB3_PROVIDER || 'ws://localhost:63546'}',
|
web3Provider: '${process.env.CIC_WEB3_PROVIDER || 'wss://bloxberg-ws.dev.grassrootseconomics.net'}',
|
||||||
cicUssdUrl: '${process.env.CIC_USSD_URL || 'https://ussd.dev.grassrootseconomics.net'}',
|
cicUssdUrl: '${process.env.CIC_USSD_URL || 'https://ussd.dev.grassrootseconomics.net'}',
|
||||||
registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0x6Ca3cB14aA6F761712E1C18646AfBA4d5Ae249E8'}',
|
registryAddress: '${process.env.CIC_REGISTRY_ADDRESS || '0xea6225212005e86a4490018ded4bf37f3e772161'}',
|
||||||
trustedDeclaratorAddress: '${process.env.CIC_TRUSTED_ADDRESS || '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C'}'
|
trustedDeclaratorAddress: '${process.env.CIC_TRUSTED_ADDRESS || '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C'}'
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// @ts-ignore
|
import { environment } from '@src/environments/environment';
|
||||||
import * as accountIndex from '@src/assets/js/block-sync/data/AccountRegistry.json';
|
import Web3 from 'web3';
|
||||||
import {environment} from '@src/environments/environment';
|
|
||||||
const Web3 = require('web3');
|
|
||||||
|
|
||||||
const web3 = new Web3(environment.web3Provider);
|
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountsIndex.json');
|
||||||
const abi = accountIndex.default;
|
const web3: Web3 = new Web3(environment.web3Provider);
|
||||||
|
|
||||||
export class AccountIndex {
|
export class AccountIndex {
|
||||||
contractAddress: string;
|
contractAddress: string;
|
||||||
@ -22,32 +20,31 @@ export class AccountIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async totalAccounts(): Promise<number> {
|
public async totalAccounts(): Promise<number> {
|
||||||
return await this.contract.methods.count().call();
|
return await this.contract.methods.entryCount().call();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async haveAccount(address: string): Promise<boolean> {
|
public async haveAccount(address: string): Promise<boolean> {
|
||||||
return await this.contract.methods.accountIndex(address).call() !== 0;
|
return (await this.contract.methods.have(address).call()) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async last(numberOfAccounts: number): Promise<Array<string>> {
|
public async last(numberOfAccounts: number): Promise<Array<string>> {
|
||||||
const count = await this.totalAccounts();
|
const count: number = await this.totalAccounts();
|
||||||
let lowest = count - numberOfAccounts - 1;
|
let lowest: number = count - numberOfAccounts;
|
||||||
if (lowest < 0) {
|
if (lowest < 0) {
|
||||||
lowest = 0;
|
lowest = 0;
|
||||||
}
|
}
|
||||||
let accounts = [];
|
const accounts: Array<string> = [];
|
||||||
for (let i = count - 1; i > lowest; i--) {
|
for (let i = count - 1; i >= lowest; i--) {
|
||||||
const account = await this.contract.methods.accounts(i).call();
|
const account: string = await this.contract.methods.entry(i).call();
|
||||||
accounts.push(account);
|
accounts.push(account);
|
||||||
}
|
}
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addToAccountRegistry(address: string): Promise<boolean> {
|
public async addToAccountRegistry(address: string): Promise<boolean> {
|
||||||
if (!await this.haveAccount(address)) {
|
if (!(await this.haveAccount(address))) {
|
||||||
return await this.contract.methods.add(address).send({from: this.signerAddress});
|
return await this.contract.methods.add(address).send({ from: this.signerAddress });
|
||||||
} else {
|
|
||||||
return await this.haveAccount(address);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export * from '@app/_eth/accountIndex';
|
export * from '@app/_eth/accountIndex';
|
||||||
export * from '@app/_eth/registry';
|
|
||||||
export * from '@app/_eth/token-registry';
|
export * from '@app/_eth/token-registry';
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
import { Registry } from '@app/_eth/registry';
|
|
||||||
import {environment} from '@src/environments/environment';
|
|
||||||
|
|
||||||
describe('Registry', () => {
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(new Registry(environment.registryAddress)).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,32 +0,0 @@
|
|||||||
// @ts-ignore
|
|
||||||
import * as registry from '@src/assets/js/block-sync/data/Registry.json';
|
|
||||||
import {environment} from '@src/environments/environment';
|
|
||||||
const Web3 = require('web3');
|
|
||||||
|
|
||||||
const web3 = new Web3(environment.web3Provider);
|
|
||||||
const abi = registry.default;
|
|
||||||
|
|
||||||
export class Registry {
|
|
||||||
contractAddress: string;
|
|
||||||
signerAddress: string;
|
|
||||||
contract: any;
|
|
||||||
|
|
||||||
constructor(contractAddress: string, signerAddress?: string) {
|
|
||||||
this.contractAddress = contractAddress;
|
|
||||||
this.contract = new web3.eth.Contract(abi, contractAddress);
|
|
||||||
if (signerAddress) {
|
|
||||||
this.signerAddress = signerAddress;
|
|
||||||
} else {
|
|
||||||
this.signerAddress = web3.eth.accounts[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async owner(): Promise<string> {
|
|
||||||
return await this.contract.methods.owner().call();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async addressOf(identifier: string): Promise<string> {
|
|
||||||
const id = '0x' + web3.utils.padRight(new Buffer(identifier).toString('hex'), 64);
|
|
||||||
return await this.contract.methods.addressOf(id).call();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { TokenRegistry } from '@app/_eth/token-registry';
|
import { TokenRegistry } from '@app/_eth/token-registry';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
|
|
||||||
describe('TokenRegistry', () => {
|
describe('TokenRegistry', () => {
|
||||||
it('should create an instance', () => {
|
it('should create an instance', () => {
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// @ts-ignore
|
|
||||||
import * as registryClient from '@src/assets/js/block-sync/data/RegistryClient.json';
|
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
|
|
||||||
const web3 = new Web3(environment.web3Provider);
|
const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json');
|
||||||
const abi = registryClient.default;
|
const web3: Web3 = new Web3(environment.web3Provider);
|
||||||
|
|
||||||
export class TokenRegistry {
|
export class TokenRegistry {
|
||||||
contractAddress: string;
|
contractAddress: string;
|
||||||
@ -22,7 +20,7 @@ export class TokenRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async totalTokens(): Promise<number> {
|
public async totalTokens(): Promise<number> {
|
||||||
return await this.contract.methods.registryCount().call();
|
return await this.contract.methods.entryCount().call();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async entry(serial: number): Promise<string> {
|
public async entry(serial: number): Promise<string> {
|
||||||
@ -30,7 +28,7 @@ export class TokenRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async addressOf(identifier: string): Promise<string> {
|
public async addressOf(identifier: string): Promise<string> {
|
||||||
const id = '0x' + web3.utils.padRight(new Buffer(identifier).toString('hex'), 64);
|
const id: string = web3.eth.abi.encodeParameter('bytes32', web3.utils.toHex(identifier));
|
||||||
return await this.contract.methods.addressOf(id).call();
|
return await this.contract.methods.addressOf(id).call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,27 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthGuard implements CanActivate {
|
export class AuthGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(private router: Router) {}
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
canActivate(
|
canActivate(
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
state: RouterStateSnapshot
|
||||||
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
|
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||||
|
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router.navigate(['/auth']);
|
this.router.navigate(['/auth']);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class RoleGuard implements CanActivate {
|
export class RoleGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(private router: Router) {}
|
constructor(private router: Router) {}
|
||||||
|
|
||||||
canActivate(
|
canActivate(
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot): 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')));
|
const currentUser = JSON.parse(localStorage.getItem(atob('CICADA_USER')));
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
|
if (route.data.roles && route.data.roles.indexOf(currentUser.role) === -1) {
|
||||||
@ -21,8 +27,7 @@ export class RoleGuard implements CanActivate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url }});
|
this.router.navigate(['/auth'], { queryParams: { returnUrl: state.url } });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export class ArraySum {
|
function arraySum(arr: Array<number>): number {
|
||||||
static arraySum(arr: any[]): number {
|
return arr.reduce((accumulator, current) => accumulator + current, 0);
|
||||||
return arr.reduce((accumulator, current) => accumulator + current, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { arraySum };
|
||||||
|
51
src/app/_helpers/clipboard-copy.ts
Normal file
51
src/app/_helpers/clipboard-copy.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
function copyToClipboard(text: any): boolean {
|
||||||
|
// create our hidden div element
|
||||||
|
const hiddenCopy: HTMLDivElement = document.createElement('div');
|
||||||
|
// set the innerHTML of the div
|
||||||
|
hiddenCopy.innerHTML = text;
|
||||||
|
// set the position to be absolute and off the screen
|
||||||
|
hiddenCopy.classList.add('clipboard');
|
||||||
|
|
||||||
|
// check and see if the user had a text selection range
|
||||||
|
let currentRange: Range | boolean;
|
||||||
|
if (document.getSelection().rangeCount > 0) {
|
||||||
|
// the user has a text selection range, store it
|
||||||
|
currentRange = document.getSelection().getRangeAt(0);
|
||||||
|
// remove the current selection
|
||||||
|
window.getSelection().removeRange(currentRange);
|
||||||
|
} else {
|
||||||
|
// they didn't have anything selected
|
||||||
|
currentRange = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the div to the body
|
||||||
|
document.body.appendChild(hiddenCopy);
|
||||||
|
// create a selection range
|
||||||
|
const copyRange: Range = document.createRange();
|
||||||
|
// set the copy range to be the hidden div
|
||||||
|
copyRange.selectNode(hiddenCopy);
|
||||||
|
// add the copy range
|
||||||
|
window.getSelection().addRange(copyRange);
|
||||||
|
|
||||||
|
// since not all browsers support this, use a try block
|
||||||
|
try {
|
||||||
|
// copy the text
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch (err) {
|
||||||
|
window.alert('Your Browser Does not support this! Error : ' + err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// remove the selection range (Chrome throws a warning if we don't.)
|
||||||
|
window.getSelection().removeRange(copyRange);
|
||||||
|
// remove the hidden div
|
||||||
|
document.body.removeChild(hiddenCopy);
|
||||||
|
|
||||||
|
// return the old selection range
|
||||||
|
if (currentRange) {
|
||||||
|
window.getSelection().addRange(currentRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { copyToClipboard };
|
@ -1,9 +1,9 @@
|
|||||||
import {ErrorStateMatcher} from '@angular/material/core';
|
import { ErrorStateMatcher } from '@angular/material/core';
|
||||||
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
|
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms';
|
||||||
|
|
||||||
export class CustomErrorStateMatcher implements ErrorStateMatcher{
|
export class CustomErrorStateMatcher implements ErrorStateMatcher {
|
||||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||||
const isSubmitted = form && form.submitted;
|
const isSubmitted: boolean = form && form.submitted;
|
||||||
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
|
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {AbstractControl, ValidationErrors} from '@angular/forms';
|
import { AbstractControl, ValidationErrors } from '@angular/forms';
|
||||||
|
|
||||||
export class CustomValidator {
|
export class CustomValidator {
|
||||||
static passwordMatchValidator(control: AbstractControl): void {
|
static passwordMatchValidator(control: AbstractControl): void {
|
||||||
@ -15,7 +15,7 @@ export class CustomValidator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = regex.test(control.value);
|
const valid: boolean = regex.test(control.value);
|
||||||
return valid ? null : error;
|
return valid ? null : error;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
function exportCsv(arrayData: any[], filename: string, delimiter = ','): void {
|
function exportCsv(arrayData: Array<any>, filename: string, delimiter: string = ','): void {
|
||||||
if (arrayData === undefined) { return; }
|
if (arrayData === undefined || arrayData.length === 0) {
|
||||||
const header = Object.keys(arrayData[0]).join(delimiter) + '\n';
|
alert('No data to be exported!');
|
||||||
let csv = header;
|
return;
|
||||||
arrayData.forEach(obj => {
|
}
|
||||||
let row = [];
|
let csv: string = Object.keys(arrayData[0]).join(delimiter) + '\n';
|
||||||
|
arrayData.forEach((obj) => {
|
||||||
|
const row: Array<any> = [];
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
if (obj.hasOwnProperty(key)) {
|
if (obj.hasOwnProperty(key)) {
|
||||||
row.push(obj[key]);
|
row.push(obj[key]);
|
||||||
@ -12,11 +14,10 @@ function exportCsv(arrayData: any[], filename: string, delimiter = ','): void {
|
|||||||
csv += row.join(delimiter) + '\n';
|
csv += row.join(delimiter) + '\n';
|
||||||
});
|
});
|
||||||
|
|
||||||
const csvData = new Blob([csv], {type: 'text/csv'});
|
const csvData: Blob = new Blob([csv], { type: 'text/csv' });
|
||||||
const csvUrl = URL.createObjectURL(csvData);
|
const csvUrl: string = URL.createObjectURL(csvData);
|
||||||
// csvUrl = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
|
|
||||||
|
|
||||||
let downloadLink = document.createElement('a');
|
const downloadLink: HTMLAnchorElement = document.createElement('a');
|
||||||
downloadLink.href = csvUrl;
|
downloadLink.href = csvUrl;
|
||||||
downloadLink.target = '_blank';
|
downloadLink.target = '_blank';
|
||||||
downloadLink.download = filename + '.csv';
|
downloadLink.download = filename + '.csv';
|
||||||
@ -32,6 +33,4 @@ function removeSpecialChar(str: string): string {
|
|||||||
return str.replace(/[^a-zA-Z0-9 ]/g, '');
|
return str.replace(/[^a-zA-Z0-9 ]/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { exportCsv };
|
||||||
exportCsv
|
|
||||||
};
|
|
||||||
|
@ -1,61 +1,84 @@
|
|||||||
import {ErrorHandler, Injectable} from '@angular/core';
|
import { ErrorHandler, Injectable } from '@angular/core';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {HttpErrorResponse} from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
import {Router} from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
// A generalized http response error
|
||||||
|
export class HttpError extends Error {
|
||||||
|
public status: number;
|
||||||
|
constructor(message: string, status: number) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
this.name = 'HttpError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GlobalErrorHandler extends ErrorHandler {
|
export class GlobalErrorHandler extends ErrorHandler {
|
||||||
private sentencesForWarningLogging: string[] = [];
|
private sentencesForWarningLogging: Array<string> = [];
|
||||||
|
|
||||||
constructor(
|
constructor(private loggingService: LoggingService, private router: Router) {
|
||||||
private loggingService: LoggingService,
|
|
||||||
private router: Router
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(error: any): void {
|
handleError(error: Error): void {
|
||||||
this.logError(error);
|
this.logError(error);
|
||||||
const message = error.message ? error.message : error.toString();
|
const message: string = error.message ? error.message : error.toString();
|
||||||
|
|
||||||
if (error.status) {
|
// if (error.status) {
|
||||||
error = new Error(message);
|
// error = new Error(message);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const errorTraceString = `Error message:\n${message}.\nStack trace: ${error.stack}`;
|
const errorTraceString: string = `Error message:\n${message}.\nStack trace: ${error.stack}`;
|
||||||
|
|
||||||
const isWarning = this.isWarning(errorTraceString);
|
const isWarning: boolean = this.isWarning(errorTraceString);
|
||||||
if (isWarning) {
|
if (isWarning) {
|
||||||
this.loggingService.sendWarnLevelMessage(errorTraceString, {error});
|
this.loggingService.sendWarnLevelMessage(errorTraceString, { error });
|
||||||
} else {
|
} else {
|
||||||
this.loggingService.sendErrorLevelMessage(errorTraceString, this, {error});
|
this.loggingService.sendErrorLevelMessage(errorTraceString, this, { error });
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
logError(error: any): void {
|
logError(error: any): void {
|
||||||
const route = this.router.url;
|
const route: string = this.router.url;
|
||||||
if (error instanceof HttpErrorResponse) {
|
if (error instanceof HttpErrorResponse) {
|
||||||
this.loggingService.sendErrorLevelMessage(
|
this.loggingService.sendErrorLevelMessage(
|
||||||
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`,
|
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${
|
||||||
this, {error});
|
(error as HttpErrorResponse).status
|
||||||
|
}`,
|
||||||
|
this,
|
||||||
|
{ error }
|
||||||
|
);
|
||||||
} else if (error instanceof TypeError) {
|
} 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) {
|
} 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 {
|
} 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 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isWarning(errorTraceString: string): boolean {
|
private isWarning(errorTraceString: string): boolean {
|
||||||
let isWarning = true;
|
let isWarning: boolean = true;
|
||||||
if (errorTraceString.includes('/src/app/')) {
|
if (errorTraceString.includes('/src/app/')) {
|
||||||
isWarning = false;
|
isWarning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sentencesForWarningLogging.forEach((whiteListSentence) => {
|
this.sentencesForWarningLogging.forEach((whiteListSentence: string) => {
|
||||||
if (errorTraceString.includes(whiteListSentence)) {
|
if (errorTraceString.includes(whiteListSentence)) {
|
||||||
isWarning = true;
|
isWarning = true;
|
||||||
}
|
}
|
||||||
@ -64,3 +87,10 @@ export class GlobalErrorHandler extends ErrorHandler {
|
|||||||
return isWarning;
|
return isWarning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function rejectBody(error): { status: any; statusText: any } {
|
||||||
|
return {
|
||||||
|
status: error.status,
|
||||||
|
statusText: error.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
import { rejectBody } from '@app/_helpers/global-error-handler';
|
||||||
|
|
||||||
function HttpGetter(): void {}
|
function HttpGetter(): void {}
|
||||||
|
|
||||||
HttpGetter.prototype.get = filename => new Promise((resolve, reject) => {
|
HttpGetter.prototype.get = (filename) =>
|
||||||
fetch(filename).then(response => {
|
new Promise((resolve, reject) => {
|
||||||
if (response.ok) {
|
fetch(filename).then((response) => {
|
||||||
resolve(response.json());
|
if (response.ok) {
|
||||||
} else {
|
resolve(response.text());
|
||||||
reject(`failed with status ${response.status} : ${response.statusText}`);
|
} else {
|
||||||
}
|
reject(rejectBody(response));
|
||||||
return;
|
}
|
||||||
|
return;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
export { HttpGetter };
|
||||||
HttpGetter
|
|
||||||
};
|
|
||||||
|
@ -6,3 +6,5 @@ export * from '@app/_helpers/http-getter';
|
|||||||
export * from '@app/_helpers/global-error-handler';
|
export * from '@app/_helpers/global-error-handler';
|
||||||
export * from '@app/_helpers/export-csv';
|
export * from '@app/_helpers/export-csv';
|
||||||
export * from '@app/_helpers/read-csv';
|
export * from '@app/_helpers/read-csv';
|
||||||
|
export * from '@app/_helpers/clipboard-copy';
|
||||||
|
export * from '@app/_helpers/schema-validation';
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,31 +1,28 @@
|
|||||||
let objCsv = {
|
const objCsv: { size: number; dataFile: any } = {
|
||||||
size: 0,
|
size: 0,
|
||||||
dataFile: []
|
dataFile: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
function readCsv(input: any): any {
|
function readCsv(input: any): Array<any> | void {
|
||||||
if (input.files && input.files[0]) {
|
if (input.files && input.files[0]) {
|
||||||
let reader = new FileReader();
|
const reader: FileReader = new FileReader();
|
||||||
reader.readAsBinaryString(input.files[0]);
|
reader.readAsBinaryString(input.files[0]);
|
||||||
reader.onload = event => {
|
reader.onload = (event) => {
|
||||||
objCsv.size = event.total;
|
objCsv.size = event.total;
|
||||||
// @ts-ignore
|
|
||||||
objCsv.dataFile = event.target.result;
|
objCsv.dataFile = event.target.result;
|
||||||
return parseData(objCsv.dataFile);
|
return parseData(objCsv.dataFile);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseData(data: any): any {
|
function parseData(data: any): Array<any> {
|
||||||
let csvData = [];
|
const csvData: Array<any> = [];
|
||||||
const lineBreak = data.split('\n');
|
const lineBreak: Array<any> = data.split('\n');
|
||||||
lineBreak.forEach(res => {
|
lineBreak.forEach((res) => {
|
||||||
csvData.push(res.split(','));
|
csvData.push(res.split(','));
|
||||||
});
|
});
|
||||||
console.table(csvData);
|
console.table(csvData);
|
||||||
return csvData;
|
return csvData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { readCsv };
|
||||||
readCsv
|
|
||||||
};
|
|
||||||
|
19
src/app/_helpers/schema-validation.ts
Normal file
19
src/app/_helpers/schema-validation.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { validatePerson, validateVcard } from '@cicnet/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}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function vcardValidation(vcard: any): Promise<void> {
|
||||||
|
const vcardValidationErrors: any = await validateVcard(vcard);
|
||||||
|
|
||||||
|
if (vcardValidationErrors) {
|
||||||
|
vcardValidationErrors.map((error) => console.error(`${error.message}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { personValidation, vcardValidation };
|
@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { ErrorInterceptor } from '@app/_interceptors/error.interceptor';
|
import { ErrorInterceptor } from '@app/_interceptors/error.interceptor';
|
||||||
|
|
||||||
describe('ErrorInterceptor', () => {
|
describe('ErrorInterceptor', () => {
|
||||||
beforeEach(() => TestBed.configureTestingModule({
|
beforeEach(() =>
|
||||||
providers: [
|
TestBed.configureTestingModule({
|
||||||
ErrorInterceptor
|
providers: [ErrorInterceptor],
|
||||||
]
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor);
|
const interceptor: ErrorInterceptor = TestBed.inject(ErrorInterceptor);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import {Injectable, isDevMode} from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpHandler,
|
HttpHandler,
|
||||||
HttpEvent,
|
HttpEvent,
|
||||||
HttpInterceptor, HttpErrorResponse
|
HttpInterceptor,
|
||||||
|
HttpErrorResponse,
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import {Observable, throwError} from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import {catchError, retry} from 'rxjs/operators';
|
import { catchError } from 'rxjs/operators';
|
||||||
import {ErrorDialogService, LoggingService} from '@app/_services';
|
import { ErrorDialogService, LoggingService } from '@app/_services';
|
||||||
import {Router} from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ErrorInterceptor implements HttpInterceptor {
|
export class ErrorInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private errorDialogService: ErrorDialogService,
|
private errorDialogService: ErrorDialogService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
@ -22,22 +22,24 @@ export class ErrorInterceptor implements HttpInterceptor {
|
|||||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
return next.handle(request).pipe(
|
return next.handle(request).pipe(
|
||||||
catchError((err: HttpErrorResponse) => {
|
catchError((err: HttpErrorResponse) => {
|
||||||
let errorMessage;
|
let errorMessage: string;
|
||||||
if (err.error instanceof ErrorEvent) {
|
if (err.error instanceof ErrorEvent) {
|
||||||
// A client-side or network error occurred. Handle it accordingly.
|
// A client-side or network error occurred. Handle it accordingly.
|
||||||
errorMessage = `An error occurred: ${err.error.message}`;
|
errorMessage = `An error occurred: ${err.error.message}`;
|
||||||
} else {
|
} else {
|
||||||
// The backend returned an unsuccessful response code.
|
// The backend returned an unsuccessful response code.
|
||||||
// The response body may contain clues as to what went wrong.
|
// The response body may contain clues as to what went wrong.
|
||||||
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(err.error)}`;
|
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(
|
||||||
|
err.error
|
||||||
|
)}`;
|
||||||
}
|
}
|
||||||
this.loggingService.sendErrorLevelMessage(errorMessage, this, {error: err});
|
this.loggingService.sendErrorLevelMessage(errorMessage, this, { error: err });
|
||||||
switch (err.status) {
|
switch (err.status) {
|
||||||
case 401: // unauthorized
|
case 401: // unauthorized
|
||||||
this.router.navigateByUrl('/auth').then();
|
this.router.navigateByUrl('/auth').then();
|
||||||
break;
|
break;
|
||||||
case 403: // forbidden
|
case 403: // forbidden
|
||||||
location.reload(true);
|
alert('Access to resource is not allowed!');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Return an observable with a user-facing error message.
|
// Return an observable with a user-facing error message.
|
||||||
|
@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { HttpConfigInterceptor } from './http-config.interceptor';
|
import { HttpConfigInterceptor } from './http-config.interceptor';
|
||||||
|
|
||||||
describe('HttpConfigInterceptor', () => {
|
describe('HttpConfigInterceptor', () => {
|
||||||
beforeEach(() => TestBed.configureTestingModule({
|
beforeEach(() =>
|
||||||
providers: [
|
TestBed.configureTestingModule({
|
||||||
HttpConfigInterceptor
|
providers: [HttpConfigInterceptor],
|
||||||
]
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
const interceptor: HttpConfigInterceptor = TestBed.inject(HttpConfigInterceptor);
|
const interceptor: HttpConfigInterceptor = TestBed.inject(HttpConfigInterceptor);
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {
|
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
|
||||||
HttpRequest,
|
|
||||||
HttpHandler,
|
|
||||||
HttpEvent,
|
|
||||||
HttpInterceptor
|
|
||||||
} from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HttpConfigInterceptor implements HttpInterceptor {
|
export class HttpConfigInterceptor implements HttpInterceptor {
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
const token = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
// const token: string = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
||||||
|
|
||||||
if (token) {
|
// if (token) {
|
||||||
request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)});
|
// request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)});
|
||||||
}
|
// }
|
||||||
|
|
||||||
return next.handle(request);
|
return next.handle(request);
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
|
|||||||
import { LoggingInterceptor } from './logging.interceptor';
|
import { LoggingInterceptor } from './logging.interceptor';
|
||||||
|
|
||||||
describe('LoggingInterceptor', () => {
|
describe('LoggingInterceptor', () => {
|
||||||
beforeEach(() => TestBed.configureTestingModule({
|
beforeEach(() =>
|
||||||
providers: [
|
TestBed.configureTestingModule({
|
||||||
LoggingInterceptor
|
providers: [LoggingInterceptor],
|
||||||
]
|
})
|
||||||
}));
|
);
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
const interceptor: LoggingInterceptor = TestBed.inject(LoggingInterceptor);
|
const interceptor: LoggingInterceptor = TestBed.inject(LoggingInterceptor);
|
||||||
|
@ -4,34 +4,32 @@ import {
|
|||||||
HttpHandler,
|
HttpHandler,
|
||||||
HttpEvent,
|
HttpEvent,
|
||||||
HttpInterceptor,
|
HttpInterceptor,
|
||||||
HttpResponse
|
HttpResponse,
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import {Observable} from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {finalize, tap} from 'rxjs/operators';
|
import { finalize, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoggingInterceptor implements HttpInterceptor {
|
export class LoggingInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private loggingService: LoggingService) {}
|
||||||
constructor(
|
|
||||||
private loggingService: LoggingService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
this.loggingService.sendInfoLevelMessage(request);
|
return next.handle(request);
|
||||||
const startTime = Date.now();
|
// this.loggingService.sendInfoLevelMessage(request);
|
||||||
let status: string;
|
// const startTime: number = Date.now();
|
||||||
|
// let status: string;
|
||||||
return next.handle(request).pipe(tap(event => {
|
//
|
||||||
status = '';
|
// return next.handle(request).pipe(tap(event => {
|
||||||
if (event instanceof HttpResponse) {
|
// status = '';
|
||||||
status = 'succeeded';
|
// if (event instanceof HttpResponse) {
|
||||||
}
|
// status = 'succeeded';
|
||||||
}, error => status = 'failed'),
|
// }
|
||||||
finalize(() => {
|
// }, error => status = 'failed'),
|
||||||
const elapsedTime = Date.now() - startTime;
|
// finalize(() => {
|
||||||
const message = `${request.method} request for ${request.urlWithParams} ${status} in ${elapsedTime} ms`;
|
// const elapsedTime: number = Date.now() - startTime;
|
||||||
this.loggingService.sendInfoLevelMessage(message);
|
// const message: string = `${request.method} request for ${request.urlWithParams} ${status} in ${elapsedTime} ms`;
|
||||||
}));
|
// this.loggingService.sendInfoLevelMessage(message);
|
||||||
|
// }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
export interface AccountDetails {
|
interface AccountDetails {
|
||||||
date_registered: number;
|
date_registered: number;
|
||||||
gender: string;
|
gender: string;
|
||||||
age?: string;
|
age?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
balance?: number;
|
||||||
identities: {
|
identities: {
|
||||||
evm: {
|
evm: {
|
||||||
'bloxberg:8996': string[];
|
'bloxberg:8996': string[];
|
||||||
@ -19,46 +20,56 @@ export interface AccountDetails {
|
|||||||
products: string[];
|
products: string[];
|
||||||
category?: string;
|
category?: string;
|
||||||
vcard: {
|
vcard: {
|
||||||
email: [{
|
email: [
|
||||||
value: string;
|
{
|
||||||
}];
|
value: string;
|
||||||
fn: [{
|
}
|
||||||
value: string;
|
];
|
||||||
}];
|
fn: [
|
||||||
n: [{
|
{
|
||||||
value: string[];
|
value: string;
|
||||||
}];
|
}
|
||||||
tel: [{
|
];
|
||||||
meta: {
|
n: [
|
||||||
TYP: string[];
|
{
|
||||||
},
|
value: string[];
|
||||||
value: string;
|
}
|
||||||
}],
|
];
|
||||||
version: [{
|
tel: [
|
||||||
value: string;
|
{
|
||||||
}];
|
meta: {
|
||||||
|
TYP: string[];
|
||||||
|
};
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
version: [
|
||||||
|
{
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Signature {
|
interface Signature {
|
||||||
algo: string;
|
algo: string;
|
||||||
data: string;
|
data: string;
|
||||||
digest: string;
|
digest: string;
|
||||||
engine: string;
|
engine: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Meta {
|
interface Meta {
|
||||||
data: AccountDetails;
|
data: AccountDetails;
|
||||||
id: string;
|
id: string;
|
||||||
signature: Signature;
|
signature: Signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MetaResponse {
|
interface MetaResponse {
|
||||||
id: string;
|
id: string;
|
||||||
m: Meta;
|
m: Meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultAccount: AccountDetails = {
|
const defaultAccount: AccountDetails = {
|
||||||
date_registered: Date.now(),
|
date_registered: Date.now(),
|
||||||
gender: 'other',
|
gender: 'other',
|
||||||
identities: {
|
identities: {
|
||||||
@ -74,23 +85,35 @@ export const defaultAccount: AccountDetails = {
|
|||||||
},
|
},
|
||||||
products: [],
|
products: [],
|
||||||
vcard: {
|
vcard: {
|
||||||
email: [{
|
email: [
|
||||||
value: '',
|
{
|
||||||
}],
|
value: '',
|
||||||
fn: [{
|
|
||||||
value: 'Sarafu Contract',
|
|
||||||
}],
|
|
||||||
n: [{
|
|
||||||
value: ['Sarafu', 'Contract'],
|
|
||||||
}],
|
|
||||||
tel: [{
|
|
||||||
meta: {
|
|
||||||
TYP: [],
|
|
||||||
},
|
},
|
||||||
value: '',
|
],
|
||||||
}],
|
fn: [
|
||||||
version: [{
|
{
|
||||||
value: '3.0',
|
value: 'Sarafu Contract',
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
|
n: [
|
||||||
|
{
|
||||||
|
value: ['Sarafu', 'Contract'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tel: [
|
||||||
|
{
|
||||||
|
meta: {
|
||||||
|
TYP: [],
|
||||||
|
},
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
version: [
|
||||||
|
{
|
||||||
|
value: '3.0',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { AccountDetails, Signature, Meta, MetaResponse, defaultAccount };
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export * from '@app/_models/transaction';
|
export * from '@app/_models/transaction';
|
||||||
export * from '@app/_models/settings';
|
export * from '@app/_models/settings';
|
||||||
export * from '@app/_models/user';
|
|
||||||
export * from '@app/_models/account';
|
export * from '@app/_models/account';
|
||||||
|
export * from '@app/_models/staff';
|
||||||
|
export * from '@app/_models/token';
|
||||||
|
export * from '@app/_models/mappings';
|
||||||
|
24
src/app/_models/mappings.ts
Normal file
24
src/app/_models/mappings.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
interface Action {
|
||||||
|
id: number;
|
||||||
|
user: string;
|
||||||
|
role: string;
|
||||||
|
action: string;
|
||||||
|
approval: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
name: string;
|
||||||
|
products: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AreaName {
|
||||||
|
name: string;
|
||||||
|
locations: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AreaType {
|
||||||
|
name: string;
|
||||||
|
area: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Action, Category, AreaName, AreaType };
|
@ -1,4 +1,4 @@
|
|||||||
export class Settings {
|
class Settings {
|
||||||
w3: W3 = {
|
w3: W3 = {
|
||||||
engine: undefined,
|
engine: undefined,
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
@ -12,7 +12,9 @@ export class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class W3 {
|
class W3 {
|
||||||
engine: any;
|
engine: any;
|
||||||
provider: any;
|
provider: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { Settings, W3 };
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
export interface Staff {
|
interface Staff {
|
||||||
comment: string;
|
comment: string;
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
tag: number;
|
tag: number;
|
||||||
userid: string;
|
userid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { Staff };
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface Token {
|
interface Token {
|
||||||
name: string;
|
name: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
address: string;
|
address: string;
|
||||||
@ -8,8 +8,10 @@ export interface Token {
|
|||||||
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
|
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
|
||||||
weight: string;
|
weight: string;
|
||||||
balance: string;
|
balance: string;
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
reserveRatio?: string;
|
reserveRatio?: string;
|
||||||
owner?: string;
|
owner?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { Token };
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {User} from '@app/_models/user';
|
import { AccountDetails } from '@app/_models/account';
|
||||||
|
|
||||||
export class BlocksBloom {
|
class BlocksBloom {
|
||||||
low: number;
|
low: number;
|
||||||
blockFilter: string;
|
blockFilter: string;
|
||||||
blocktxFilter: string;
|
blocktxFilter: string;
|
||||||
@ -8,13 +8,13 @@ export class BlocksBloom {
|
|||||||
filterRounds: number;
|
filterRounds: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Token {
|
class TxToken {
|
||||||
address: string;
|
address: string;
|
||||||
name: string;
|
name: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tx {
|
class Tx {
|
||||||
block: number;
|
block: number;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
@ -22,22 +22,25 @@ export class Tx {
|
|||||||
txIndex: number;
|
txIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Transaction {
|
class Transaction {
|
||||||
from: string;
|
from: string;
|
||||||
sender: User;
|
sender: AccountDetails;
|
||||||
to: string;
|
to: string;
|
||||||
recipient: User;
|
recipient: AccountDetails;
|
||||||
token: Token;
|
token: TxToken;
|
||||||
tx: Tx;
|
tx: Tx;
|
||||||
value: number;
|
value: number;
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Conversion {
|
class Conversion {
|
||||||
destinationToken: Token;
|
destinationToken: TxToken;
|
||||||
fromValue: number;
|
fromValue: number;
|
||||||
sourceToken: Token;
|
sourceToken: TxToken;
|
||||||
toValue: number;
|
toValue: number;
|
||||||
trader: string;
|
trader: string;
|
||||||
user: User;
|
user: AccountDetails;
|
||||||
tx: Tx;
|
tx: Tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { BlocksBloom, TxToken, Tx, Transaction, Conversion };
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
export class User {
|
|
||||||
dateRegistered: number;
|
|
||||||
vcard: {
|
|
||||||
fn: string;
|
|
||||||
version: string;
|
|
||||||
tel: [{
|
|
||||||
meta: {
|
|
||||||
TYP: string;
|
|
||||||
};
|
|
||||||
value: string[];
|
|
||||||
}];
|
|
||||||
};
|
|
||||||
key: {
|
|
||||||
ethereum: string[];
|
|
||||||
};
|
|
||||||
location: {
|
|
||||||
latitude: string;
|
|
||||||
longitude: string;
|
|
||||||
external: {};
|
|
||||||
};
|
|
||||||
selling: string[];
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { KeyStore } from 'cic-client-meta';
|
import { KeyStore } from 'cic-client-meta';
|
||||||
// TODO should we put this on the mutalble key store object
|
// TODO should we put this on the mutable key store object
|
||||||
import * as openpgp from 'openpgp';
|
import * as openpgp from 'openpgp';
|
||||||
const keyring = new openpgp.Keyring();
|
const keyring = new openpgp.Keyring();
|
||||||
|
|
||||||
@ -31,8 +31,7 @@ interface MutableKeyStore extends KeyStore {
|
|||||||
sign(plainText: string, passphrase: string): Promise<any>;
|
sign(plainText: string, passphrase: string): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutablePgpKeyStore implements MutableKeyStore{
|
class MutablePgpKeyStore implements MutableKeyStore {
|
||||||
|
|
||||||
async loadKeyring(): Promise<void> {
|
async loadKeyring(): Promise<void> {
|
||||||
await keyring.load();
|
await keyring.load();
|
||||||
await keyring.store();
|
await keyring.store();
|
||||||
@ -76,15 +75,14 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
|||||||
}
|
}
|
||||||
|
|
||||||
async isValidKey(key): Promise<boolean> {
|
async isValidKey(key): Promise<boolean> {
|
||||||
// There is supposed to be an opengpg.readKey() method but I can't find it?
|
// There is supposed to be an openpgp.readKey() method but I can't find it?
|
||||||
const _key = await openpgp.key.readArmored(key);
|
const testKey = await openpgp.key.readArmored(key);
|
||||||
return !_key.err;
|
return !testKey.err;
|
||||||
}
|
}
|
||||||
|
|
||||||
async isEncryptedPrivateKey(privateKey: any): Promise<boolean> {
|
async isEncryptedPrivateKey(privateKey: any): Promise<boolean> {
|
||||||
const imported = await openpgp.key.readArmored(privateKey);
|
const imported = await openpgp.key.readArmored(privateKey);
|
||||||
for (let i = 0; i < imported.keys.length; i++) {
|
for (const key of imported.keys) {
|
||||||
const key = imported.keys[i];
|
|
||||||
if (key.isDecrypted()) {
|
if (key.isDecrypted()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -94,8 +92,12 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
|||||||
|
|
||||||
getFingerprint(): string {
|
getFingerprint(): string {
|
||||||
// TODO Handle multiple keys
|
// TODO Handle multiple keys
|
||||||
return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].keyPacket &&
|
return (
|
||||||
keyring.privateKeys.keys[0].keyPacket.fingerprint;
|
keyring.privateKeys &&
|
||||||
|
keyring.privateKeys.keys[0] &&
|
||||||
|
keyring.privateKeys.keys[0].keyPacket &&
|
||||||
|
keyring.privateKeys.keys[0].keyPacket.fingerprint
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getKeyId(key: any): string {
|
getKeyId(key: any): string {
|
||||||
@ -104,7 +106,11 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
|||||||
|
|
||||||
getPrivateKeyId(): string {
|
getPrivateKeyId(): string {
|
||||||
// TODO is there a library that comes with angular for doing this?
|
// TODO is there a library that comes with angular for doing this?
|
||||||
return 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> {
|
getKeysForId(keyId: string): Array<any> {
|
||||||
@ -136,7 +142,7 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
|||||||
}
|
}
|
||||||
|
|
||||||
removePublicKey(publicKey: any): any {
|
removePublicKey(publicKey: any): any {
|
||||||
const keyId = publicKey.getKeyId().toHex();
|
const keyId = publicKey.getKeyId().toHex();
|
||||||
return keyring.publicKeys.removeForId(keyId);
|
return keyring.publicKeys.removeForId(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +165,4 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { MutablePgpKeyStore, MutableKeyStore };
|
||||||
MutablePgpKeyStore,
|
|
||||||
MutableKeyStore
|
|
||||||
};
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PGPSigner } from '@app/_pgp/pgp-signer';
|
import { PGPSigner } from '@app/_pgp/pgp-signer';
|
||||||
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp/pgp-key-store';
|
import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp/pgp-key-store';
|
||||||
const keystore: MutableKeyStore = new MutablePgpKeyStore();
|
const keystore: MutableKeyStore = new MutablePgpKeyStore();
|
||||||
|
|
||||||
describe('PgpSigner', () => {
|
describe('PgpSigner', () => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {MutableKeyStore} from '@app/_pgp/pgp-key-store';
|
import { MutableKeyStore } from '@app/_pgp/pgp-key-store';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
|
|
||||||
const openpgp = require('openpgp');
|
const openpgp = require('openpgp');
|
||||||
|
|
||||||
@ -7,12 +7,12 @@ interface Signable {
|
|||||||
digest(): string;
|
digest(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Signature = {
|
interface Signature {
|
||||||
engine: string
|
engine: string;
|
||||||
algo: string
|
algo: string;
|
||||||
data: string
|
data: string;
|
||||||
digest: string;
|
digest: string;
|
||||||
};
|
}
|
||||||
|
|
||||||
interface Signer {
|
interface Signer {
|
||||||
onsign(signature: Signature): void;
|
onsign(signature: Signature): void;
|
||||||
@ -24,7 +24,6 @@ interface Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PGPSigner implements Signer {
|
class PGPSigner implements Signer {
|
||||||
|
|
||||||
engine = 'pgp';
|
engine = 'pgp';
|
||||||
algo = 'sha256';
|
algo = 'sha256';
|
||||||
dgst: string;
|
dgst: string;
|
||||||
@ -50,28 +49,35 @@ class PGPSigner implements Signer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public verify(digest: string, signature: Signature): void {
|
public verify(digest: string, signature: Signature): void {
|
||||||
openpgp.signature.readArmored(signature.data).then((sig) => {
|
openpgp.signature
|
||||||
const opts = {
|
.readArmored(signature.data)
|
||||||
message: openpgp.cleartext.fromText(digest),
|
.then((sig) => {
|
||||||
publicKeys: this.keyStore.getTrustedKeys(),
|
const opts = {
|
||||||
signature: sig,
|
message: openpgp.cleartext.fromText(digest),
|
||||||
};
|
publicKeys: this.keyStore.getTrustedKeys(),
|
||||||
openpgp.verify(opts).then((v) => {
|
signature: sig,
|
||||||
let i = 0;
|
};
|
||||||
for (i = 0; i < v.signatures.length; i++) {
|
openpgp.verify(opts).then((v) => {
|
||||||
const s = v.signatures[i];
|
let i = 0;
|
||||||
if (s.valid) {
|
for (i = 0; i < v.signatures.length; i++) {
|
||||||
this.onverify(s);
|
const s = v.signatures[i];
|
||||||
return;
|
if (s.valid) {
|
||||||
|
this.onverify(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
this.loggingService.sendErrorLevelMessage(
|
||||||
this.loggingService.sendErrorLevelMessage(`Checked ${i} signature(s) but none valid`, this, {error: '404 Not found!'});
|
`Checked ${i} signature(s) but none valid`,
|
||||||
|
this,
|
||||||
|
{ error: '404 Not found!' }
|
||||||
|
);
|
||||||
|
this.onverify(false);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.loggingService.sendErrorLevelMessage(e.message, this, { error: e });
|
||||||
this.onverify(false);
|
this.onverify(false);
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
|
||||||
this.loggingService.sendErrorLevelMessage(e.message, this, {error: e});
|
|
||||||
this.onverify(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async sign(digest: string): Promise<void> {
|
public async sign(digest: string): Promise<void> {
|
||||||
@ -86,25 +92,23 @@ class PGPSigner implements Signer {
|
|||||||
privateKeys: [pk],
|
privateKeys: [pk],
|
||||||
detached: true,
|
detached: true,
|
||||||
};
|
};
|
||||||
openpgp.sign(opts).then((s) => {
|
openpgp
|
||||||
this.signature = {
|
.sign(opts)
|
||||||
engine: this.engine,
|
.then((s) => {
|
||||||
algo: this.algo,
|
this.signature = {
|
||||||
data: s.signature,
|
engine: this.engine,
|
||||||
// TODO: fix for browser later
|
algo: this.algo,
|
||||||
digest,
|
data: s.signature,
|
||||||
};
|
// TODO: fix for browser later
|
||||||
this.onsign(this.signature);
|
digest,
|
||||||
}).catch((e) => {
|
};
|
||||||
this.loggingService.sendErrorLevelMessage(e.message, this, {error: e});
|
this.onsign(this.signature);
|
||||||
this.onsign(undefined);
|
})
|
||||||
});
|
.catch((e) => {
|
||||||
|
this.loggingService.sendErrorLevelMessage(e.message, this, { error: e });
|
||||||
|
this.onsign(undefined);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Signable, Signature, Signer, PGPSigner };
|
||||||
Signable,
|
|
||||||
Signature,
|
|
||||||
Signer,
|
|
||||||
PGPSigner
|
|
||||||
};
|
|
||||||
|
@ -1,32 +1,38 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js';
|
import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js';
|
||||||
import { signChallenge } from '@src/assets/js/hoba-pgp.js';
|
import { signChallenge } from '@src/assets/js/hoba-pgp.js';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp';
|
import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp';
|
||||||
import {ErrorDialogService} from '@app/_services/error-dialog.service';
|
import { ErrorDialogService } from '@app/_services/error-dialog.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {Observable } from 'rxjs';
|
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
sessionToken: any;
|
sessionToken: any;
|
||||||
privateKey: any;
|
privateKey: any;
|
||||||
mutableKeyStore: MutableKeyStore = new MutablePgpKeyStore();
|
mutableKeyStore: MutableKeyStore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private errorDialogService: ErrorDialogService
|
private errorDialogService: ErrorDialogService
|
||||||
) {
|
) {
|
||||||
// TODO setting these together shoulds be atomic
|
this.mutableKeyStore = new MutablePgpKeyStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
await this.mutableKeyStore.loadKeyring();
|
||||||
|
// TODO setting these together should be atomic
|
||||||
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
|
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
|
||||||
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
||||||
}
|
}
|
||||||
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
||||||
this.privateKey = localStorage.getItem(btoa('CICADA_PRIVATE_KEY'));
|
this.privateKey = localStorage.getItem(btoa('CICADA_PRIVATE_KEY'));
|
||||||
|
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,61 +40,59 @@ export class AuthService {
|
|||||||
document.getElementById('state').innerHTML = s;
|
document.getElementById('state').innerHTML = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWithToken(): void {
|
getWithToken(): Promise<boolean> {
|
||||||
const headers = {
|
return new Promise((resolve, reject) => {
|
||||||
Authorization: 'Bearer ' + this.sessionToken,
|
const headers = {
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
Authorization: 'Bearer ' + this.sessionToken,
|
||||||
'x-cic-automerge': 'none'
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
};
|
'x-cic-automerge': 'none',
|
||||||
const options = {
|
};
|
||||||
headers,
|
const options = {
|
||||||
};
|
headers,
|
||||||
fetch(environment.cicMetaUrl, options).then(response => {
|
};
|
||||||
if (response.status === 401) {
|
fetch(environment.cicMetaUrl, options).then((response) => {
|
||||||
return Promise.reject({
|
if (response.status === 401) {
|
||||||
status: response.status,
|
return reject(rejectBody(response));
|
||||||
statusText: response.statusText
|
}
|
||||||
});
|
return resolve(true);
|
||||||
}
|
});
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResponse(hobaResponseEncoded): void {
|
// TODO rename to send signed challenge and set session. Also separate these responsibilities
|
||||||
const headers = {
|
sendResponse(hobaResponseEncoded: any): Promise<boolean> {
|
||||||
Authorization: 'HOBA ' + hobaResponseEncoded,
|
return new Promise((resolve, reject) => {
|
||||||
'Content-Type': 'application/json;charset=utf-8',
|
const headers = {
|
||||||
'x-cic-automerge': 'none'
|
Authorization: 'HOBA ' + hobaResponseEncoded,
|
||||||
};
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
const options = {
|
'x-cic-automerge': 'none',
|
||||||
headers,
|
};
|
||||||
};
|
const options = {
|
||||||
fetch(environment.cicMetaUrl, options).then(response => {
|
headers,
|
||||||
if (response.status === 401) {
|
};
|
||||||
return Promise.reject({
|
fetch(environment.cicMetaUrl, options).then((response) => {
|
||||||
status: response.status,
|
if (response.status === 401) {
|
||||||
statusText: response.statusText
|
return reject(rejectBody(response));
|
||||||
});
|
}
|
||||||
}
|
this.sessionToken = response.headers.get('Token');
|
||||||
this.sessionToken = response.headers.get('Token');
|
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
|
||||||
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
|
this.setState('Click button to log in');
|
||||||
this.setState('Click button to log in');
|
return resolve(true);
|
||||||
return;
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChallenge(): Promise<any> {
|
getChallenge(): Promise<any> {
|
||||||
return fetch(environment.cicMetaUrl).then(async response => {
|
return new Promise((resolve, reject) => {
|
||||||
if (response.status === 401) {
|
fetch(environment.cicMetaUrl).then(async (response) => {
|
||||||
const authHeader = response.headers.get('WWW-Authenticate');
|
if (response.status === 401) {
|
||||||
return hobaParseChallengeHeader(authHeader);
|
const authHeader: string = response.headers.get('WWW-Authenticate');
|
||||||
}
|
return resolve(hobaParseChallengeHeader(authHeader));
|
||||||
if (!response.ok) {
|
}
|
||||||
return Promise.reject({
|
if (!response.ok) {
|
||||||
status: response.status,
|
return reject(rejectBody(response));
|
||||||
statusText: response.statusText
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +108,7 @@ export class AuthService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
login(): boolean {
|
async login(): Promise<boolean> {
|
||||||
if (this.sessionToken !== undefined) {
|
if (this.sessionToken !== undefined) {
|
||||||
try {
|
try {
|
||||||
this.getWithToken();
|
this.getWithToken();
|
||||||
@ -117,18 +121,39 @@ export class AuthService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loginResponse(o: { challenge: string; realm: any }): Promise<any> {
|
||||||
async loginResponse(o: any, password: string): Promise<any> {
|
return new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const r = await signChallenge(o.challenge, o.realm, environment.cicMetaUrl, this.mutableKeyStore, password);
|
const r = await signChallenge(
|
||||||
this.sendResponse(r);
|
o.challenge,
|
||||||
} catch (error) {
|
o.realm,
|
||||||
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'});
|
environment.cicMetaUrl,
|
||||||
return Promise.reject({
|
this.mutableKeyStore
|
||||||
status: error.status,
|
);
|
||||||
statusText: error.statusText
|
const response: boolean = await this.sendResponse(r);
|
||||||
});
|
resolve(response);
|
||||||
}
|
} catch (error) {
|
||||||
|
if (error instanceof HttpError) {
|
||||||
|
if (error.status === 403) {
|
||||||
|
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. ' +
|
||||||
|
'Please speak with the staff at Grassroots ' +
|
||||||
|
'Economics for requesting access ' +
|
||||||
|
'staff@grassrootseconomics.net.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO define this error
|
||||||
|
this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
|
||||||
|
}
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setKey(privateKeyArmored): Promise<boolean> {
|
async setKey(privateKeyArmored): Promise<boolean> {
|
||||||
@ -144,7 +169,11 @@ export class AuthService {
|
|||||||
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
||||||
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.loggingService.sendErrorLevelMessage(`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({
|
this.errorDialogService.openDialog({
|
||||||
message: `Failed to set key: ${err.message || err.statusText}`,
|
message: `Failed to set key: ${err.message || err.statusText}`,
|
||||||
});
|
});
|
||||||
@ -155,23 +184,30 @@ export class AuthService {
|
|||||||
|
|
||||||
logout(): void {
|
logout(): void {
|
||||||
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
|
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
|
||||||
|
localStorage.removeItem(btoa('CICADA_PRIVATE_KEY'));
|
||||||
this.sessionToken = undefined;
|
this.sessionToken = undefined;
|
||||||
window.location.reload(true);
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrustedUsers(): any {
|
getTrustedUsers(): any {
|
||||||
let trustedUsers = [];
|
const trustedUsers: Array<any> = [];
|
||||||
this.mutableKeyStore.getPublicKeys().forEach(key => trustedUsers.push(key.users[0].userId));
|
this.mutableKeyStore.getPublicKeys().forEach((key) => trustedUsers.push(key.users[0].userId));
|
||||||
return trustedUsers;
|
return trustedUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPublicKeys(): Observable<any> {
|
async getPublicKeys(): Promise<any> {
|
||||||
return this.httpClient.get(`${environment.publicKeysUrl}`, {responseType: 'text'});
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch(environment.publicKeysUrl).then((res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
// TODO does angular recommend an error interface?
|
||||||
|
return reject(rejectBody(res));
|
||||||
|
}
|
||||||
|
return resolve(res.text());
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrivateKeys(): Promise<void> {
|
getPrivateKey(): any {
|
||||||
if (this.privateKey !== undefined) {
|
return this.mutableKeyStore.getPrivateKey();
|
||||||
await this.mutableKeyStore.importPrivateKey(this.privateKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { BlockSyncService } from '@app/_services/block-sync.service';
|
import { BlockSyncService } from '@app/_services/block-sync.service';
|
||||||
import {TransactionService} from '@app/_services/transaction.service';
|
import { TransactionService } from '@app/_services/transaction.service';
|
||||||
import {TransactionServiceStub} from '@src/testing';
|
import { TransactionServiceStub } from '@src/testing';
|
||||||
|
|
||||||
describe('BlockSyncService', () => {
|
describe('BlockSyncService', () => {
|
||||||
let service: BlockSyncService;
|
let service: BlockSyncService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [{ provide: TransactionService, useClass: TransactionServiceStub }],
|
||||||
{ provide: TransactionService, useClass: TransactionServiceStub }
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
service = TestBed.inject(BlockSyncService);
|
service = TestBed.inject(BlockSyncService);
|
||||||
});
|
});
|
||||||
|
@ -1,36 +1,32 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {Settings} from '@app/_models';
|
import { Settings } from '@app/_models';
|
||||||
import Web3 from 'web3';
|
import { TransactionHelper } from 'cic-client';
|
||||||
import {CICRegistry, TransactionHelper} from 'cic-client';
|
import { first } from 'rxjs/operators';
|
||||||
import {first} from 'rxjs/operators';
|
import { TransactionService } from '@app/_services/transaction.service';
|
||||||
import {TransactionService} from '@app/_services/transaction.service';
|
import { environment } from '@src/environments/environment';
|
||||||
import {environment} from '@src/environments/environment';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {HttpGetter} from '@app/_helpers';
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class BlockSyncService {
|
export class BlockSyncService {
|
||||||
readyStateTarget: number = 2;
|
readyStateTarget: number = 2;
|
||||||
readyState: number = 0;
|
readyState: number = 0;
|
||||||
fileGetter = new HttpGetter();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private loggingService: LoggingService
|
private loggingService: LoggingService,
|
||||||
) { }
|
private registryService: RegistryService
|
||||||
|
) {}
|
||||||
|
|
||||||
blockSync(address: string = null, offset: number = 0, limit: number = 100): any {
|
blockSync(address: string = null, offset: number = 0, limit: number = 100): void {
|
||||||
this.transactionService.resetTransactionsList();
|
this.transactionService.resetTransactionsList();
|
||||||
const settings = new Settings(this.scan);
|
const settings: Settings = new Settings(this.scan);
|
||||||
const provider = environment.web3Provider;
|
const readyStateElements: { network: number } = { network: 2 };
|
||||||
const readyStateElements = { network: 2 };
|
settings.w3.provider = environment.web3Provider;
|
||||||
settings.w3.provider = provider;
|
settings.w3.engine = this.registryService.getWeb3();
|
||||||
settings.w3.engine = new Web3(provider);
|
settings.registry = this.registryService.getRegistry();
|
||||||
settings.registry = new CICRegistry(settings.w3.engine, environment.registryAddress, this.fileGetter,
|
|
||||||
['../../assets/js/block-sync/data']);
|
|
||||||
settings.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress);
|
|
||||||
settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry);
|
settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry);
|
||||||
|
|
||||||
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
|
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
|
||||||
@ -47,10 +43,17 @@ export class BlockSyncService {
|
|||||||
settings.registry.load();
|
settings.registry.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
readyStateProcessor(settings: Settings, bit: number, address: string, offset: number, limit: number): void {
|
readyStateProcessor(
|
||||||
|
settings: Settings,
|
||||||
|
bit: number,
|
||||||
|
address: string,
|
||||||
|
offset: number,
|
||||||
|
limit: number
|
||||||
|
): void {
|
||||||
|
// tslint:disable-next-line:no-bitwise
|
||||||
this.readyState |= bit;
|
this.readyState |= bit;
|
||||||
if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
|
if (this.readyStateTarget === this.readyState && this.readyStateTarget) {
|
||||||
const wHeadSync = new Worker('./../assets/js/block-sync/head.js');
|
const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js');
|
||||||
wHeadSync.onmessage = (m) => {
|
wHeadSync.onmessage = (m) => {
|
||||||
settings.txHelper.processReceipt(m.data);
|
settings.txHelper.processReceipt(m.data);
|
||||||
};
|
};
|
||||||
@ -58,18 +61,24 @@ export class BlockSyncService {
|
|||||||
w3_provider: settings.w3.provider,
|
w3_provider: settings.w3.provider,
|
||||||
});
|
});
|
||||||
if (address === null) {
|
if (address === null) {
|
||||||
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
|
this.transactionService
|
||||||
this.fetcher(settings, res);
|
.getAllTransactions(offset, limit)
|
||||||
});
|
.pipe(first())
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.fetcher(settings, res);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => {
|
this.transactionService
|
||||||
this.fetcher(settings, res);
|
.getAddressTransactions(address, offset, limit)
|
||||||
});
|
.pipe(first())
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.fetcher(settings, res);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newTransferEvent(tx): any {
|
newTransferEvent(tx: any): any {
|
||||||
return new CustomEvent('cic_transfer', {
|
return new CustomEvent('cic_transfer', {
|
||||||
detail: {
|
detail: {
|
||||||
tx,
|
tx,
|
||||||
@ -77,7 +86,7 @@ export class BlockSyncService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
newConversionEvent(tx): any {
|
newConversionEvent(tx: any): any {
|
||||||
return new CustomEvent('cic_convert', {
|
return new CustomEvent('cic_convert', {
|
||||||
detail: {
|
detail: {
|
||||||
tx,
|
tx,
|
||||||
@ -85,8 +94,15 @@ export class BlockSyncService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async scan(settings, lo, hi, bloomBlockBytes, bloomBlocktxBytes, bloomRounds): Promise<void> {
|
async scan(
|
||||||
const w = new Worker('./../assets/js/block-sync/ondemand.js');
|
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) => {
|
w.onmessage = (m) => {
|
||||||
settings.txHelper.processReceipt(m.data);
|
settings.txHelper.processReceipt(m.data);
|
||||||
};
|
};
|
||||||
@ -94,23 +110,27 @@ export class BlockSyncService {
|
|||||||
w3_provider: settings.w3.provider,
|
w3_provider: settings.w3.provider,
|
||||||
lo,
|
lo,
|
||||||
hi,
|
hi,
|
||||||
filters: [
|
filters: [bloomBlockBytes, bloomBlocktxBytes],
|
||||||
bloomBlockBytes,
|
|
||||||
bloomBlocktxBytes,
|
|
||||||
],
|
|
||||||
filter_rounds: bloomRounds,
|
filter_rounds: bloomRounds,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetcher(settings: Settings, transactionsInfo: any): void {
|
fetcher(settings: Settings, transactionsInfo: any): void {
|
||||||
const blockFilterBinstr = window.atob(transactionsInfo.block_filter);
|
const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter);
|
||||||
const bOne = new Uint8Array(blockFilterBinstr.length);
|
const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length);
|
||||||
bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i));
|
bOne.map((e, i, v) => (v[i] = blockFilterBinstr.charCodeAt(i)));
|
||||||
|
|
||||||
const blocktxFilterBinstr = window.atob(transactionsInfo.blocktx_filter);
|
const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter);
|
||||||
const bTwo = new Uint8Array(blocktxFilterBinstr.length);
|
const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length);
|
||||||
bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i));
|
bTwo.map((e, i, v) => (v[i] = blocktxFilterBinstr.charCodeAt(i)));
|
||||||
|
|
||||||
settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds);
|
settings.scanFilter(
|
||||||
|
settings,
|
||||||
|
transactionsInfo.low,
|
||||||
|
transactionsInfo.high,
|
||||||
|
bOne,
|
||||||
|
bTwo,
|
||||||
|
transactionsInfo.filter_rounds
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,25 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {MatDialog} from '@angular/material/dialog';
|
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
|
||||||
import {ErrorDialogComponent} from '@app/shared/error-dialog/error-dialog.component';
|
import { ErrorDialogComponent } from '@app/shared/error-dialog/error-dialog.component';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ErrorDialogService {
|
export class ErrorDialogService {
|
||||||
public isDialogOpen: boolean = false;
|
public isDialogOpen: boolean = false;
|
||||||
|
|
||||||
constructor(
|
constructor(public dialog: MatDialog) {}
|
||||||
public dialog: MatDialog,
|
|
||||||
) { }
|
|
||||||
|
|
||||||
openDialog(data): any {
|
openDialog(data): any {
|
||||||
if (this.isDialogOpen) {
|
if (this.isDialogOpen) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.isDialogOpen = true;
|
this.isDialogOpen = true;
|
||||||
const dialogRef = this.dialog.open(ErrorDialogComponent, {
|
const dialogRef: MatDialogRef<any> = this.dialog.open(ErrorDialogComponent, {
|
||||||
width: '300px',
|
width: '300px',
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(() => this.isDialogOpen = false);
|
dialogRef.afterClosed().subscribe(() => (this.isDialogOpen = false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,30 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {BehaviorSubject} from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import {first} from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class LocationService {
|
export class LocationService {
|
||||||
locations: any = '';
|
constructor(private httpClient: HttpClient) {}
|
||||||
private locationsList = new BehaviorSubject<any>(this.locations);
|
|
||||||
locationsSubject = this.locationsList.asObservable();
|
|
||||||
|
|
||||||
constructor(
|
getAreaNames(): Observable<any> {
|
||||||
private httpClient: HttpClient,
|
return this.httpClient.get(`${environment.cicMetaUrl}/areanames`);
|
||||||
) { }
|
}
|
||||||
|
|
||||||
getLocations(): void {
|
getAreaNameByLocation(location: string): Observable<any> {
|
||||||
this.httpClient.get(`${environment.cicCacheUrl}/locations`).pipe(first()).subscribe(res => this.locationsList.next(res));
|
return this.httpClient.get(`${environment.cicMetaUrl}/areanames/${location.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAreaTypes(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/areatypes`).pipe(first());
|
||||||
|
}
|
||||||
|
|
||||||
|
getAreaTypeByArea(area: string): Observable<any> {
|
||||||
|
return this.httpClient
|
||||||
|
.get(`${environment.cicMetaUrl}/areatypes/${area.toLowerCase()}`)
|
||||||
|
.pipe(first());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {Injectable, isDevMode} from '@angular/core';
|
import { Injectable, isDevMode } from '@angular/core';
|
||||||
import {NGXLogger} from 'ngx-logger';
|
import { NGXLogger } from 'ngx-logger';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class LoggingService {
|
export class LoggingService {
|
||||||
env: string;
|
env: string;
|
||||||
@ -15,31 +15,31 @@ export class LoggingService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendTraceLevelMessage(message, source, error): void {
|
sendTraceLevelMessage(message: any, source: any, error: any): void {
|
||||||
this.logger.trace(message, source, error);
|
this.logger.trace(message, source, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendDebugLevelMessage(message, source, error): void {
|
sendDebugLevelMessage(message: any, source: any, error: any): void {
|
||||||
this.logger.debug(message, source, error);
|
this.logger.debug(message, source, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendInfoLevelMessage(message): void {
|
sendInfoLevelMessage(message: any): void {
|
||||||
this.logger.info(message);
|
this.logger.info(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendLogLevelMessage(message, source, error): void {
|
sendLogLevelMessage(message: any, source: any, error: any): void {
|
||||||
this.logger.log(message, source, error);
|
this.logger.log(message, source, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendWarnLevelMessage(message, error): void {
|
sendWarnLevelMessage(message: any, error: any): void {
|
||||||
this.logger.warn(message, error);
|
this.logger.warn(message, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendErrorLevelMessage(message, source, error): void {
|
sendErrorLevelMessage(message: any, source: any, error: any): void {
|
||||||
this.logger.error(message, source, error);
|
this.logger.error(message, source, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFatalLevelMessage(message, source, error): void {
|
sendFatalLevelMessage(message: any, source: any, error: any): void {
|
||||||
this.logger.fatal(message, source, error);
|
this.logger.fatal(message, source, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
src/app/_services/registry.service.spec.ts
Normal file
16
src/app/_services/registry.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RegistryService } from './registry.service';
|
||||||
|
|
||||||
|
describe('RegistryService', () => {
|
||||||
|
let service: RegistryService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(RegistryService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
33
src/app/_services/registry.service.ts
Normal file
33
src/app/_services/registry.service.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class RegistryService {
|
||||||
|
web3: Web3 = new Web3(environment.web3Provider);
|
||||||
|
fileGetter: FileGetter = new HttpGetter();
|
||||||
|
registry: CICRegistry = new CICRegistry(
|
||||||
|
this.web3,
|
||||||
|
environment.registryAddress,
|
||||||
|
'Registry',
|
||||||
|
this.fileGetter,
|
||||||
|
['../../assets/js/block-sync/data']
|
||||||
|
);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress);
|
||||||
|
this.registry.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRegistry(): any {
|
||||||
|
return this.registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeb3(): any {
|
||||||
|
return this.web3;
|
||||||
|
}
|
||||||
|
}
|
@ -1,32 +1,33 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { EventEmitter, Injectable } from '@angular/core';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import {HttpGetter} from '@app/_helpers';
|
import { CICRegistry } from 'cic-client';
|
||||||
import {CICRegistry} from 'cic-client';
|
import { TokenRegistry } from '@app/_eth';
|
||||||
import Web3 from 'web3';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {Registry, TokenRegistry} from '@app/_eth';
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class TokenService {
|
export class TokenService {
|
||||||
web3 = new Web3(environment.web3Provider);
|
registry: CICRegistry;
|
||||||
fileGetter = new HttpGetter();
|
tokenRegistry: TokenRegistry;
|
||||||
registry = new Registry(environment.registryAddress);
|
LoadEvent: EventEmitter<number> = new EventEmitter<number>();
|
||||||
cicRegistry = new CICRegistry(this.web3, environment.registryAddress, this.fileGetter, ['../../assets/js/block-sync/data']);
|
|
||||||
tokens: any = '';
|
|
||||||
private tokensList = new BehaviorSubject<any>(this.tokens);
|
|
||||||
tokensSubject = this.tokensList.asObservable();
|
|
||||||
|
|
||||||
constructor(
|
constructor(private httpClient: HttpClient, private registryService: RegistryService) {
|
||||||
private httpClient: HttpClient,
|
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.LoadEvent.next(Date.now());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async getTokens(): Promise<any> {
|
async getTokens(): Promise<Array<Promise<string>>> {
|
||||||
const tokenRegistryQuery = new TokenRegistry(await this.registry.addressOf('TokenRegistry'));
|
const count: number = await this.tokenRegistry.totalTokens();
|
||||||
const count = await tokenRegistryQuery.totalTokens();
|
return Array.from({ length: count }, async (v, i) => await this.tokenRegistry.entry(i));
|
||||||
return Array.from({length: count}, async (v, i) => await tokenRegistryQuery.entry(i));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTokenBySymbol(symbol: string): Observable<any> {
|
getTokenBySymbol(symbol: string): Observable<any> {
|
||||||
@ -34,8 +35,7 @@ export class TokenService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getTokenBalance(address: string): Promise<number> {
|
async getTokenBalance(address: string): Promise<number> {
|
||||||
const tokenRegistryQuery = new TokenRegistry(await this.registry.addressOf('TokenRegistry'));
|
const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0));
|
||||||
const sarafuToken = await this.cicRegistry.addToken(await tokenRegistryQuery.entry(0));
|
|
||||||
return await sarafuToken.methods.balanceOf(address).call();
|
return await sarafuToken.methods.balanceOf(address).call();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { TransactionService } from '@app/_services/transaction.service';
|
import { TransactionService } from '@app/_services/transaction.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
|
|
||||||
describe('TransactionService', () => {
|
describe('TransactionService', () => {
|
||||||
let httpClient: HttpClient;
|
let httpClient: HttpClient;
|
||||||
@ -11,7 +11,7 @@ describe('TransactionService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [HttpClientTestingModule]
|
imports: [HttpClientTestingModule],
|
||||||
});
|
});
|
||||||
httpClient = TestBed.inject(HttpClient);
|
httpClient = TestBed.inject(HttpClient);
|
||||||
httpTestingController = TestBed.inject(HttpTestingController);
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
|
@ -1,40 +1,46 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {first} from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import {Envelope, User} from 'cic-client-meta';
|
import { Envelope, User } from 'cic-client-meta';
|
||||||
import {UserService} from '@app/_services/user.service';
|
import { UserService } from '@app/_services/user.service';
|
||||||
import { Keccak } from 'sha3';
|
import { Keccak } from 'sha3';
|
||||||
import { utils } from 'ethers';
|
import { utils } from 'ethers';
|
||||||
import {add0x, fromHex, strip0x, toHex} from '@src/assets/js/ethtx/dist/hex';
|
import { add0x, fromHex, strip0x, toHex } from '@src/assets/js/ethtx/dist/hex';
|
||||||
import {Tx} from '@src/assets/js/ethtx/dist';
|
import { Tx } from '@src/assets/js/ethtx/dist';
|
||||||
import {toValue} from '@src/assets/js/ethtx/dist/tx';
|
import { toValue } from '@src/assets/js/ethtx/dist/tx';
|
||||||
import * as secp256k1 from 'secp256k1';
|
import * as secp256k1 from 'secp256k1';
|
||||||
import {AuthService} from '@app/_services/auth.service';
|
import { AuthService } from '@app/_services/auth.service';
|
||||||
import {defaultAccount} from '@app/_models';
|
import { defaultAccount } from '@app/_models';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {Registry} from '@app/_eth';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { CICRegistry } from 'cic-client';
|
||||||
const Web3 = require('web3');
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
|
import Web3 from 'web3';
|
||||||
const vCard = require('vcard-parser');
|
const vCard = require('vcard-parser');
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class TransactionService {
|
export class TransactionService {
|
||||||
transactions: any[] = [];
|
transactions: any[] = [];
|
||||||
private transactionList = new BehaviorSubject<any[]>(this.transactions);
|
private transactionList = new BehaviorSubject<any[]>(this.transactions);
|
||||||
transactionsSubject = this.transactionList.asObservable();
|
transactionsSubject = this.transactionList.asObservable();
|
||||||
userInfo: any;
|
userInfo: any;
|
||||||
web3 = new Web3(environment.web3Provider);
|
web3: Web3;
|
||||||
registry = new Registry(environment.registryAddress);
|
registry: CICRegistry;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private loggingService: LoggingService
|
private loggingService: LoggingService,
|
||||||
) { }
|
private registryService: RegistryService
|
||||||
|
) {
|
||||||
|
this.web3 = this.registryService.getWeb3();
|
||||||
|
this.registry = registryService.getRegistry();
|
||||||
|
this.registry.load();
|
||||||
|
}
|
||||||
|
|
||||||
getAllTransactions(offset: number, limit: number): Observable<any> {
|
getAllTransactions(offset: number, limit: number): Observable<any> {
|
||||||
return this.httpClient.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`);
|
return this.httpClient.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`);
|
||||||
@ -45,36 +51,58 @@ export class TransactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async setTransaction(transaction, cacheSize: number): Promise<void> {
|
async setTransaction(transaction, cacheSize: number): Promise<void> {
|
||||||
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === transaction.tx.txHash)) { return; }
|
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === transaction.tx.txHash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
transaction.value = Number(transaction.value);
|
transaction.value = Number(transaction.value);
|
||||||
transaction.type = 'transaction';
|
transaction.type = 'transaction';
|
||||||
try {
|
try {
|
||||||
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.from)).pipe(first()).subscribe((res) => {
|
this.userService
|
||||||
transaction.sender = this.getAccountInfo(res.body);
|
.getAccountDetailsFromMeta(await User.toKey(transaction.from))
|
||||||
}, error => {
|
.pipe(first())
|
||||||
transaction.sender = defaultAccount;
|
.subscribe(
|
||||||
});
|
(res) => {
|
||||||
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe((res) => {
|
transaction.sender = this.getAccountInfo(res.body);
|
||||||
transaction.recipient = this.getAccountInfo(res.body);
|
},
|
||||||
}, error => {
|
(error) => {
|
||||||
transaction.recipient = defaultAccount;
|
transaction.sender = defaultAccount;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
this.userService
|
||||||
|
.getAccountDetailsFromMeta(await User.toKey(transaction.to))
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe(
|
||||||
|
(res) => {
|
||||||
|
transaction.recipient = this.getAccountInfo(res.body);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
transaction.recipient = defaultAccount;
|
||||||
|
}
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.addTransaction(transaction, cacheSize);
|
this.addTransaction(transaction, cacheSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async setConversion(conversion, cacheSize): Promise<void> {
|
async setConversion(conversion, cacheSize): Promise<void> {
|
||||||
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === conversion.tx.txHash)) { return; }
|
if (this.transactions.find((cachedTx) => cachedTx.tx.txHash === conversion.tx.txHash)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
conversion.type = 'conversion';
|
conversion.type = 'conversion';
|
||||||
conversion.fromValue = Number(conversion.fromValue);
|
conversion.fromValue = Number(conversion.fromValue);
|
||||||
conversion.toValue = Number(conversion.toValue);
|
conversion.toValue = Number(conversion.toValue);
|
||||||
try {
|
try {
|
||||||
this.userService.getAccountDetailsFromMeta(await User.toKey(conversion.trader)).pipe(first()).subscribe((res) => {
|
this.userService
|
||||||
conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
|
.getAccountDetailsFromMeta(await User.toKey(conversion.trader))
|
||||||
}, error => {
|
.pipe(first())
|
||||||
conversion.sender = conversion.recipient = defaultAccount;
|
.subscribe(
|
||||||
});
|
(res) => {
|
||||||
|
conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
conversion.sender = conversion.recipient = defaultAccount;
|
||||||
|
}
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.addTransaction(conversion, cacheSize);
|
this.addTransaction(conversion, cacheSize);
|
||||||
}
|
}
|
||||||
@ -94,29 +122,39 @@ export class TransactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAccountInfo(account: string): any {
|
getAccountInfo(account: string): any {
|
||||||
let accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
|
const accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
|
||||||
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
||||||
return accountInfo;
|
return accountInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
async transferRequest(tokenAddress: string, senderAddress: string, recipientAddress: string, value: number): Promise<any> {
|
async transferRequest(
|
||||||
const transferAuthAddress = await this.registry.addressOf('TransferAuthorization');
|
tokenAddress: string,
|
||||||
|
senderAddress: string,
|
||||||
|
recipientAddress: string,
|
||||||
|
value: number
|
||||||
|
): Promise<any> {
|
||||||
|
const transferAuthAddress = await this.registry.getContractAddressByName(
|
||||||
|
'TransferAuthorization'
|
||||||
|
);
|
||||||
const hashFunction = new Keccak(256);
|
const hashFunction = new Keccak(256);
|
||||||
hashFunction.update('createRequest(address,address,address,uint256)');
|
hashFunction.update('createRequest(address,address,address,uint256)');
|
||||||
const hash = hashFunction.digest();
|
const hash = hashFunction.digest();
|
||||||
const methodSignature = hash.toString('hex').substring(0, 8);
|
const methodSignature = hash.toString('hex').substring(0, 8);
|
||||||
const abiCoder = new utils.AbiCoder();
|
const abiCoder = new utils.AbiCoder();
|
||||||
const abi = await abiCoder.encode(['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 data = fromHex(methodSignature + strip0x(abi));
|
||||||
const tx = new Tx(environment.bloxbergChainId);
|
const tx = new Tx(environment.bloxbergChainId);
|
||||||
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);
|
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);
|
||||||
tx.gasPrice = await this.web3.eth.getGasPrice();
|
tx.gasPrice = Number(await this.web3.eth.getGasPrice());
|
||||||
tx.gasLimit = 8000000;
|
tx.gasLimit = 8000000;
|
||||||
tx.to = fromHex(strip0x(transferAuthAddress));
|
tx.to = fromHex(strip0x(transferAuthAddress));
|
||||||
tx.value = toValue(value);
|
tx.value = toValue(value);
|
||||||
tx.data = data;
|
tx.data = data;
|
||||||
const txMsg = tx.message();
|
const txMsg = tx.message();
|
||||||
const privateKey = this.authService.mutableKeyStore.getPrivateKey();
|
const privateKey = this.authService.mutableKeyStore.getPrivateKey();
|
||||||
if (!privateKey.isDecrypted()) {
|
if (!privateKey.isDecrypted()) {
|
||||||
const password = window.prompt('password');
|
const password = window.prompt('password');
|
||||||
await privateKey.decrypt(password);
|
await privateKey.decrypt(password);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { UserService } from '@app/_services/user.service';
|
import { UserService } from '@app/_services/user.service';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
||||||
let httpClient: HttpClient;
|
let httpClient: HttpClient;
|
||||||
@ -11,7 +11,7 @@ describe('UserService', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [HttpClientTestingModule]
|
imports: [HttpClientTestingModule],
|
||||||
});
|
});
|
||||||
httpClient = TestBed.inject(HttpClient);
|
httpClient = TestBed.inject(HttpClient);
|
||||||
httpTestingController = TestBed.inject(HttpTestingController);
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
@ -24,18 +24,18 @@ describe('UserService', () => {
|
|||||||
|
|
||||||
it('should return user for available id', () => {
|
it('should return user for available id', () => {
|
||||||
expect(service.getAccountById(1)).toEqual({
|
expect(service.getAccountById(1)).toEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'John Doe',
|
name: 'John Doe',
|
||||||
phone: '+25412345678',
|
phone: '+25412345678',
|
||||||
address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865',
|
address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865',
|
||||||
type: 'user',
|
type: 'user',
|
||||||
created: '08/16/2020',
|
created: '08/16/2020',
|
||||||
balance: '12987',
|
balance: '12987',
|
||||||
failedPinAttempts: 1,
|
failedPinAttempts: 1,
|
||||||
status: 'approved',
|
status: 'approved',
|
||||||
bio: 'Bodaboda',
|
bio: 'Bodaboda',
|
||||||
gender: 'male'
|
gender: 'male',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not return user for unavailable id', () => {
|
it('should not return user for unavailable id', () => {
|
||||||
@ -48,7 +48,7 @@ describe('UserService', () => {
|
|||||||
user: 'Tom',
|
user: 'Tom',
|
||||||
role: 'enroller',
|
role: 'enroller',
|
||||||
action: 'Disburse RSV 100',
|
action: 'Disburse RSV 100',
|
||||||
approval: false
|
approval: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ describe('UserService', () => {
|
|||||||
user: 'Tom',
|
user: 'Tom',
|
||||||
role: 'enroller',
|
role: 'enroller',
|
||||||
action: 'Disburse RSV 100',
|
action: 'Disburse RSV 100',
|
||||||
approval: true
|
approval: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ describe('UserService', () => {
|
|||||||
user: 'Christine',
|
user: 'Christine',
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
action: 'Change user phone number',
|
action: 'Change user phone number',
|
||||||
approval: false
|
approval: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,64 +1,90 @@
|
|||||||
import {Injectable} from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {BehaviorSubject, Observable} from 'rxjs';
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
import {HttpClient, HttpHeaders, HttpParams} from '@angular/common/http';
|
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
|
||||||
import {environment} from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import {first} from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import {ArgPair, Envelope, Syncable, User} from 'cic-client-meta';
|
import { ArgPair, Envelope, Phone, Syncable, User } from 'cic-client-meta';
|
||||||
import {MetaResponse} from '@app/_models';
|
import { AccountDetails } from '@app/_models';
|
||||||
import {LoggingService} from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import {TokenService} from '@app/_services/token.service';
|
import { TokenService } from '@app/_services/token.service';
|
||||||
import {AccountIndex, Registry} from '@app/_eth';
|
import { AccountIndex } from '@app/_eth';
|
||||||
import {MutableKeyStore, MutablePgpKeyStore, PGPSigner, Signer} from '@app/_pgp';
|
import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp';
|
||||||
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
|
import { CICRegistry } from 'cic-client';
|
||||||
|
import { AuthService } from '@app/_services/auth.service';
|
||||||
|
import { personValidation, vcardValidation } from '@app/_helpers';
|
||||||
|
import { add0x } from '@src/assets/js/ethtx/dist/hex';
|
||||||
const vCard = require('vcard-parser');
|
const vCard = require('vcard-parser');
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UserService {
|
export class UserService {
|
||||||
headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'});
|
headers: HttpHeaders = new HttpHeaders({ 'x-cic-automerge': 'client' });
|
||||||
keystore: MutableKeyStore = new MutablePgpKeyStore();
|
keystore: MutableKeyStore;
|
||||||
signer: Signer = new PGPSigner(this.keystore);
|
signer: Signer;
|
||||||
registry = new Registry(environment.registryAddress);
|
registry: CICRegistry;
|
||||||
|
|
||||||
accountsMeta = [];
|
accounts: Array<AccountDetails> = [];
|
||||||
accounts: any = [];
|
private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<
|
||||||
private accountsList = new BehaviorSubject<any>(this.accounts);
|
Array<AccountDetails>
|
||||||
accountsSubject = this.accountsList.asObservable();
|
>(this.accounts);
|
||||||
|
accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable();
|
||||||
|
|
||||||
actions: any = '';
|
actions: Array<any> = [];
|
||||||
private actionsList = new BehaviorSubject<any>(this.actions);
|
private actionsList: BehaviorSubject<any> = new BehaviorSubject<any>(this.actions);
|
||||||
actionsSubject = this.actionsList.asObservable();
|
actionsSubject: Observable<Array<any>> = this.actionsList.asObservable();
|
||||||
|
|
||||||
staff: any = '';
|
|
||||||
private staffList = new BehaviorSubject<any>(this.staff);
|
|
||||||
staffSubject = this.staffList.asObservable();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private tokenService: TokenService
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPin(phone: string): Observable<any> {
|
resetPin(phone: string): Observable<any> {
|
||||||
const params = new HttpParams().set('phoneNumber', phone);
|
const params: HttpParams = new HttpParams().set('phoneNumber', phone);
|
||||||
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
|
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountStatus(phone: string): any {
|
getAccountStatus(phone: string): Observable<any> {
|
||||||
const params = new HttpParams().set('phoneNumber', phone);
|
const params: HttpParams = new HttpParams().set('phoneNumber', phone);
|
||||||
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
|
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
getLockedAccounts(offset: number, limit: number): any {
|
getLockedAccounts(offset: number, limit: number): Observable<any> {
|
||||||
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
|
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string,
|
async changeAccountInfo(
|
||||||
businessCategory: string, userLocation: string, location: string, locationType: string, metaAccount: MetaResponse
|
address: string,
|
||||||
|
name: string,
|
||||||
|
phoneNumber: string,
|
||||||
|
age: string,
|
||||||
|
type: string,
|
||||||
|
bio: string,
|
||||||
|
gender: string,
|
||||||
|
businessCategory: string,
|
||||||
|
userLocation: string,
|
||||||
|
location: string,
|
||||||
|
locationType: string
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
let reqBody = metaAccount;
|
const accountInfo: any = {
|
||||||
let accountInfo = reqBody.m.data;
|
vcard: {
|
||||||
|
fn: [{}],
|
||||||
|
n: [{}],
|
||||||
|
tel: [{}],
|
||||||
|
},
|
||||||
|
location: {},
|
||||||
|
};
|
||||||
accountInfo.vcard.fn[0].value = name;
|
accountInfo.vcard.fn[0].value = name;
|
||||||
accountInfo.vcard.n[0].value = name.split(' ');
|
accountInfo.vcard.n[0].value = name.split(' ');
|
||||||
accountInfo.vcard.tel[0].value = phoneNumber;
|
accountInfo.vcard.tel[0].value = phoneNumber;
|
||||||
@ -70,46 +96,58 @@ export class UserService {
|
|||||||
accountInfo.location.area = location;
|
accountInfo.location.area = location;
|
||||||
accountInfo.location.area_name = userLocation;
|
accountInfo.location.area_name = userLocation;
|
||||||
accountInfo.location.area_type = locationType;
|
accountInfo.location.area_type = locationType;
|
||||||
accountInfo.vcard = vCard.generate(accountInfo.vcard);
|
await vcardValidation(accountInfo.vcard);
|
||||||
reqBody.m.data = accountInfo;
|
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
|
||||||
const accountKey = await User.toKey(address);
|
const accountKey: string = await User.toKey(address);
|
||||||
this.httpClient.get(`${environment.cicMetaUrl}/${accountKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => {
|
this.getAccountDetailsFromMeta(accountKey)
|
||||||
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
.pipe(first())
|
||||||
let update = [];
|
.subscribe(
|
||||||
for (const prop in reqBody) {
|
async (res) => {
|
||||||
update.push(new ArgPair(prop, reqBody[prop]));
|
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||||
}
|
const update: Array<ArgPair> = [];
|
||||||
syncableAccount.update(update, 'client-branch');
|
for (const prop of Object.keys(accountInfo)) {
|
||||||
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
update.push(new ArgPair(prop, accountInfo[prop]));
|
||||||
}, async error => {
|
}
|
||||||
this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error});
|
syncableAccount.update(update, 'client-branch');
|
||||||
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
|
await personValidation(syncableAccount.m.data);
|
||||||
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
||||||
});
|
},
|
||||||
|
async (error) => {
|
||||||
|
this.loggingService.sendErrorLevelMessage(
|
||||||
|
'Cannot find account info in meta service',
|
||||||
|
this,
|
||||||
|
{ error }
|
||||||
|
);
|
||||||
|
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
|
||||||
|
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
||||||
|
}
|
||||||
|
);
|
||||||
return accountKey;
|
return accountKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> {
|
async updateMeta(
|
||||||
const envelope = await this.wrap(syncableAccount , this.signer);
|
syncableAccount: Syncable,
|
||||||
const reqBody = envelope.toJSON();
|
accountKey: string,
|
||||||
this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
|
headers: HttpHeaders
|
||||||
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
): Promise<any> {
|
||||||
});
|
const envelope: Envelope = await this.wrap(syncableAccount, this.signer);
|
||||||
}
|
const reqBody: string = envelope.toJSON();
|
||||||
|
this.httpClient
|
||||||
getAccounts(): void {
|
.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
|
||||||
this.httpClient.get(`${environment.cicCacheUrl}/accounts`).pipe(first()).subscribe(res => this.accountsList.next(res));
|
.pipe(first())
|
||||||
}
|
.subscribe((res) => {
|
||||||
|
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
||||||
getAccountById(id: number): Observable<any> {
|
});
|
||||||
return this.httpClient.get(`${environment.cicCacheUrl}/accounts/${id}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getActions(): void {
|
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): any {
|
getActionById(id: string): Observable<any> {
|
||||||
return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`);
|
return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,30 +159,19 @@ export class UserService {
|
|||||||
return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false });
|
return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistoryByUser(id: string): Observable<any> {
|
|
||||||
return this.httpClient.get(`${environment.cicCacheUrl}/history/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAccountDetailsFromMeta(userKey: string): Observable<any> {
|
getAccountDetailsFromMeta(userKey: string): Observable<any> {
|
||||||
return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
|
return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser(userKey: string): any {
|
|
||||||
return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers })
|
|
||||||
.pipe(first()).subscribe(async res => {
|
|
||||||
return Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap(syncable: Syncable, signer: Signer): Promise<Envelope> {
|
wrap(syncable: Syncable, signer: Signer): Promise<Envelope> {
|
||||||
return new Promise<Envelope>(async (whohoo, doh) => {
|
return new Promise<Envelope>(async (resolve, reject) => {
|
||||||
syncable.setSigner(signer);
|
syncable.setSigner(signer);
|
||||||
syncable.onwrap = async (env) => {
|
syncable.onwrap = async (env) => {
|
||||||
if (env === undefined) {
|
if (env === undefined) {
|
||||||
doh();
|
reject();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
whohoo(env);
|
resolve(env);
|
||||||
};
|
};
|
||||||
await syncable.sign();
|
await syncable.sign();
|
||||||
});
|
});
|
||||||
@ -152,28 +179,89 @@ export class UserService {
|
|||||||
|
|
||||||
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
|
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
|
||||||
this.resetAccountsList();
|
this.resetAccountsList();
|
||||||
const accountIndexAddress = await this.registry.addressOf('AccountRegistry');
|
const accountIndexAddress: string = await this.registry.getContractAddressByName(
|
||||||
|
'AccountRegistry'
|
||||||
|
);
|
||||||
const accountIndexQuery = new AccountIndex(accountIndexAddress);
|
const accountIndexQuery = new AccountIndex(accountIndexAddress);
|
||||||
const accountAddresses = await accountIndexQuery.last(await accountIndexQuery.totalAccounts());
|
const accountAddresses: Array<string> = await accountIndexQuery.last(
|
||||||
|
await accountIndexQuery.totalAccounts()
|
||||||
|
);
|
||||||
this.loggingService.sendInfoLevelMessage(accountAddresses);
|
this.loggingService.sendInfoLevelMessage(accountAddresses);
|
||||||
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
||||||
this.getAccountDetailsFromMeta(await User.toKey(accountAddress)).pipe(first()).subscribe(async res => {
|
await this.getAccountByAddress(accountAddress, limit);
|
||||||
const account = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
}
|
||||||
this.accountsMeta.push(account);
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||||
const accountInfo = account.m.data;
|
const accountInfo = account.m.data;
|
||||||
accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm['bloxberg:8996'][0]);
|
await personValidation(accountInfo);
|
||||||
|
accountInfo.balance = await this.tokenService.getTokenBalance(
|
||||||
|
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
|
||||||
|
);
|
||||||
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
||||||
|
await vcardValidation(accountInfo.vcard);
|
||||||
this.accounts.unshift(accountInfo);
|
this.accounts.unshift(accountInfo);
|
||||||
if (this.accounts.length > limit) {
|
if (this.accounts.length > limit) {
|
||||||
this.accounts.length = limit;
|
this.accounts.length = limit;
|
||||||
}
|
}
|
||||||
this.accountsList.next(this.accounts);
|
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) => {
|
||||||
|
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) => {
|
||||||
|
accountSubject.next(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return accountSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetAccountsList(): void {
|
resetAccountsList(): void {
|
||||||
this.accounts = [];
|
this.accounts = [];
|
||||||
this.accountsList.next(this.accounts);
|
this.accountsList.next(this.accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchAccountByName(name: string): any {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategories(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/categories`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCategoryByProduct(product: string): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/categories/${product.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccountTypes(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/accounttypes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTransactionTypes(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/transactiontypes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
getGenders(): Observable<any> {
|
||||||
|
return this.httpClient.get(`${environment.cicMetaUrl}/genders`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,24 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import {Routes, RouterModule, PreloadAllModules} from '@angular/router';
|
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
|
||||||
import {AuthGuard} from '@app/_guards';
|
import { AuthGuard } from '@app/_guards';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: 'auth', loadChildren: () => import('@app/auth/auth.module').then(m => m.AuthModule) },
|
{ path: 'auth', loadChildren: () => import('@app/auth/auth.module').then((m) => m.AuthModule) },
|
||||||
{ path: '', loadChildren: () => import('@pages/pages.module').then(m => m.PagesModule), canActivate: [AuthGuard] },
|
{
|
||||||
{ path: '**', redirectTo: '', pathMatch: 'full' }
|
path: '',
|
||||||
|
loadChildren: () => import('@pages/pages.module').then((m) => m.PagesModule),
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
{ path: '**', redirectTo: '', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, {
|
imports: [
|
||||||
preloadingStrategy: PreloadAllModules
|
RouterModule.forRoot(routes, {
|
||||||
})],
|
preloadingStrategy: PreloadAllModules,
|
||||||
exports: [RouterModule]
|
useHash: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule { }
|
export class AppRoutingModule {}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
<app-network-status></app-network-status>
|
||||||
<router-outlet (activate)="onResize(mediaQuery)"></router-outlet>
|
<router-outlet (activate)="onResize(mediaQuery)"></router-outlet>
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { AppComponent } from '@app/app.component';
|
import { AppComponent } from '@app/app.component';
|
||||||
import {TransactionService} from '@app/_services';
|
import { TransactionService } from '@app/_services';
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, TransactionServiceStub} from '@src/testing';
|
import {
|
||||||
|
FooterStubComponent,
|
||||||
|
SidebarStubComponent,
|
||||||
|
TopbarStubComponent,
|
||||||
|
TransactionServiceStub,
|
||||||
|
} from '@src/testing';
|
||||||
|
|
||||||
describe('AppComponent', () => {
|
describe('AppComponent', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [RouterTestingModule],
|
||||||
RouterTestingModule
|
declarations: [AppComponent, FooterStubComponent, SidebarStubComponent, TopbarStubComponent],
|
||||||
],
|
providers: [{ provide: TransactionService, useClass: TransactionServiceStub }],
|
||||||
declarations: [
|
|
||||||
AppComponent,
|
|
||||||
FooterStubComponent,
|
|
||||||
SidebarStubComponent,
|
|
||||||
TopbarStubComponent
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: TransactionService, useClass: TransactionServiceStub }
|
|
||||||
]
|
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,42 +1,68 @@
|
|||||||
import {ChangeDetectionStrategy, Component, HostListener} from '@angular/core';
|
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
|
||||||
import {AuthService, ErrorDialogService, LoggingService, TransactionService} from '@app/_services';
|
import {
|
||||||
import {catchError} from 'rxjs/operators';
|
AuthService,
|
||||||
|
ErrorDialogService,
|
||||||
|
LoggingService,
|
||||||
|
TransactionService,
|
||||||
|
} from '@app/_services';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { SwUpdate } from '@angular/service-worker';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.scss'],
|
styleUrls: ['./app.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit {
|
||||||
title = 'CICADA';
|
title = 'CICADA';
|
||||||
readyStateTarget: number = 3;
|
readyStateTarget: number = 3;
|
||||||
readyState: number = 0;
|
readyState: number = 0;
|
||||||
mediaQuery = window.matchMedia('(max-width: 768px)');
|
mediaQuery: MediaQueryList = window.matchMedia('(max-width: 768px)');
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private errorDialogService: ErrorDialogService
|
private errorDialogService: ErrorDialogService,
|
||||||
|
private swUpdate: SwUpdate
|
||||||
) {
|
) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await this.authService.mutableKeyStore.loadKeyring();
|
try {
|
||||||
this.authService.getPublicKeys()
|
await this.authService.init();
|
||||||
.pipe(catchError(async (error) => {
|
// this.authService.getPublicKeys()
|
||||||
this.loggingService.sendErrorLevelMessage('Unable to load trusted public keys.', this, {error});
|
// .pipe(catchError(async (error) => {
|
||||||
this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'});
|
// this.loggingService.sendErrorLevelMessage('Unable to load trusted public keys.', this, {error});
|
||||||
})).subscribe(this.authService.mutableKeyStore.importPublicKey);
|
// 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);
|
||||||
|
} catch (error) {
|
||||||
|
this.errorDialogService.openDialog({
|
||||||
|
message: 'Trusted keys endpoint cannot be reached. Please try again later.',
|
||||||
|
});
|
||||||
|
// TODO do something to halt user progress...show a sad cicada page 🦗?
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
this.mediaQuery.addListener(this.onResize);
|
this.mediaQuery.addEventListener('change', this.onResize);
|
||||||
this.onResize(this.mediaQuery);
|
this.onResize(this.mediaQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.swUpdate.isEnabled) {
|
||||||
|
this.swUpdate.available.subscribe(() => {
|
||||||
|
if (confirm('New Version available. Load New Version?')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load resize
|
// Load resize
|
||||||
onResize(e): void {
|
onResize(e): void {
|
||||||
const sidebar = document.getElementById('sidebar');
|
const sidebar: HTMLElement = document.getElementById('sidebar');
|
||||||
const content = document.getElementById('content');
|
const content: HTMLElement = document.getElementById('content');
|
||||||
const sidebarCollapse = document.getElementById('sidebarCollapse');
|
const sidebarCollapse: HTMLElement = document.getElementById('sidebarCollapse');
|
||||||
if (sidebarCollapse?.classList.contains('active')) {
|
if (sidebarCollapse?.classList.contains('active')) {
|
||||||
sidebarCollapse?.classList.remove('active');
|
sidebarCollapse?.classList.remove('active');
|
||||||
}
|
}
|
||||||
@ -59,13 +85,13 @@ export class AppComponent {
|
|||||||
|
|
||||||
@HostListener('window:cic_transfer', ['$event'])
|
@HostListener('window:cic_transfer', ['$event'])
|
||||||
async cicTransfer(event: CustomEvent): Promise<void> {
|
async cicTransfer(event: CustomEvent): Promise<void> {
|
||||||
const transaction = event.detail.tx;
|
const transaction: any = event.detail.tx;
|
||||||
await this.transactionService.setTransaction(transaction, 100);
|
await this.transactionService.setTransaction(transaction, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:cic_convert', ['$event'])
|
@HostListener('window:cic_convert', ['$event'])
|
||||||
async cicConvert(event: CustomEvent): Promise<void> {
|
async cicConvert(event: CustomEvent): Promise<void> {
|
||||||
const conversion = event.detail.tx;
|
const conversion: any = event.detail.tx;
|
||||||
await this.transactionService.setConversion(conversion, 100);
|
await this.transactionService.setConversion(conversion, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,36 @@
|
|||||||
import {BrowserModule} from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import {ErrorHandler, NgModule} from '@angular/core';
|
import { ErrorHandler, NgModule } from '@angular/core';
|
||||||
|
|
||||||
import {AppRoutingModule} from '@app/app-routing.module';
|
import { AppRoutingModule } from '@app/app-routing.module';
|
||||||
import {AppComponent} from '@app/app.component';
|
import { AppComponent } from '@app/app.component';
|
||||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||||
import {
|
import { GlobalErrorHandler, MockBackendProvider } from '@app/_helpers';
|
||||||
GlobalErrorHandler,
|
import { SharedModule } from '@app/shared/shared.module';
|
||||||
MockBackendProvider,
|
import { MatTableModule } from '@angular/material/table';
|
||||||
} from '@app/_helpers';
|
import { AuthGuard } from '@app/_guards';
|
||||||
import {DataTablesModule} from 'angular-datatables';
|
import { LoggerModule } from 'ngx-logger';
|
||||||
import {SharedModule} from '@app/shared/shared.module';
|
import { environment } from '@src/environments/environment';
|
||||||
import {MatTableModule} from '@angular/material/table';
|
import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors';
|
||||||
import {AuthGuard} from '@app/_guards';
|
import { MutablePgpKeyStore } from '@app/_pgp';
|
||||||
import {LoggerModule} from 'ngx-logger';
|
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||||
import {environment} from '@src/environments/environment';
|
|
||||||
import {ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor} from '@app/_interceptors';
|
|
||||||
import {MutablePgpKeyStore} from '@app/_pgp';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [AppComponent],
|
||||||
AppComponent
|
|
||||||
],
|
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
DataTablesModule,
|
|
||||||
SharedModule,
|
SharedModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
LoggerModule.forRoot({
|
LoggerModule.forRoot({
|
||||||
level: environment.logLevel,
|
level: environment.logLevel,
|
||||||
serverLogLevel: environment.serverLogLevel,
|
serverLogLevel: environment.serverLogLevel,
|
||||||
serverLoggingUrl: `${environment.loggingUrl}/api/logs/`,
|
serverLoggingUrl: `${environment.loggingUrl}/api/logs/`,
|
||||||
disableConsoleLogging: false
|
disableConsoleLogging: false,
|
||||||
})
|
}),
|
||||||
|
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
AuthGuard,
|
AuthGuard,
|
||||||
@ -47,6 +42,6 @@ import {MutablePgpKeyStore} from '@app/_pgp';
|
|||||||
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
|
||||||
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
|
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule {}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive';
|
import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive';
|
||||||
import {ElementRef, Renderer2} from '@angular/core';
|
import { ElementRef, Renderer2 } from '@angular/core';
|
||||||
|
|
||||||
|
// tslint:disable-next-line:prefer-const
|
||||||
let elementRef: ElementRef;
|
let elementRef: ElementRef;
|
||||||
|
// tslint:disable-next-line:prefer-const
|
||||||
let renderer: Renderer2;
|
let renderer: Renderer2;
|
||||||
|
|
||||||
describe('PasswordToggleDirective', () => {
|
describe('PasswordToggleDirective', () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Directive, ElementRef, Input, Renderer2} from '@angular/core';
|
import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[appPasswordToggle]'
|
selector: '[appPasswordToggle]',
|
||||||
})
|
})
|
||||||
export class PasswordToggleDirective {
|
export class PasswordToggleDirective {
|
||||||
@Input()
|
@Input()
|
||||||
@ -10,18 +10,15 @@ export class PasswordToggleDirective {
|
|||||||
@Input()
|
@Input()
|
||||||
iconId: string;
|
iconId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
|
||||||
private elementRef: ElementRef,
|
|
||||||
private renderer: Renderer2,
|
|
||||||
) {
|
|
||||||
this.renderer.listen(this.elementRef.nativeElement, 'click', () => {
|
this.renderer.listen(this.elementRef.nativeElement, 'click', () => {
|
||||||
this.togglePasswordVisibility();
|
this.togglePasswordVisibility();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePasswordVisibility(): void {
|
togglePasswordVisibility(): void {
|
||||||
const password = document.getElementById(this.id);
|
const password: HTMLElement = document.getElementById(this.id);
|
||||||
const icon = document.getElementById(this.iconId);
|
const icon: HTMLElement = document.getElementById(this.iconId);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (password.type === 'password') {
|
if (password.type === 'password') {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -5,11 +5,11 @@ import { AuthComponent } from '@app/auth/auth.component';
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: AuthComponent },
|
{ path: '', component: AuthComponent },
|
||||||
{ path: '**', redirectTo: '', pathMatch: 'full'},
|
{ path: '**', redirectTo: '', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AuthRoutingModule { }
|
export class AuthRoutingModule {}
|
||||||
|
@ -8,9 +8,8 @@ describe('AuthComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ AuthComponent ]
|
declarations: [AuthComponent],
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit} from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||||
import {AuthService} from '@app/_services';
|
import { AuthService } from '@app/_services';
|
||||||
import {Router} from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth',
|
selector: 'app-auth',
|
||||||
templateUrl: './auth.component.html',
|
templateUrl: './auth.component.html',
|
||||||
styleUrls: ['./auth.component.scss'],
|
styleUrls: ['./auth.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AuthComponent implements OnInit {
|
export class AuthComponent implements OnInit {
|
||||||
keyForm: FormGroup;
|
keyForm: FormGroup;
|
||||||
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
keyFormSubmitted: boolean = false;
|
keyFormSubmitted: boolean = false;
|
||||||
keyFormLoading: boolean = false;
|
keyFormLoading: boolean = false;
|
||||||
passwordForm: FormGroup;
|
passwordForm: FormGroup;
|
||||||
passwordFormSubmitted: boolean = false;
|
passwordFormSubmitted: boolean = false;
|
||||||
passwordFormLoading: boolean = false;
|
passwordFormLoading: boolean = false;
|
||||||
matcher = new CustomErrorStateMatcher();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
@ -50,7 +50,9 @@ export class AuthComponent implements OnInit {
|
|||||||
async onSubmit(): Promise<void> {
|
async onSubmit(): Promise<void> {
|
||||||
this.keyFormSubmitted = true;
|
this.keyFormSubmitted = true;
|
||||||
|
|
||||||
if (this.keyForm.invalid) { return; }
|
if (this.keyForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.keyFormLoading = true;
|
this.keyFormLoading = true;
|
||||||
const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
|
const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
|
||||||
@ -111,7 +113,7 @@ export class AuthComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleDisplay(element: any, active: boolean): void {
|
toggleDisplay(element: any, active: boolean): void {
|
||||||
const style = window.getComputedStyle(element).display;
|
const style: string = window.getComputedStyle(element).display;
|
||||||
if (active) {
|
if (active) {
|
||||||
element.style.display = 'block';
|
element.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,14 +3,13 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { AuthRoutingModule } from '@app/auth/auth-routing.module';
|
import { AuthRoutingModule } from '@app/auth/auth-routing.module';
|
||||||
import { AuthComponent } from '@app/auth/auth.component';
|
import { AuthComponent } from '@app/auth/auth.component';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import {PasswordToggleDirective} from '@app/auth/_directives/password-toggle.directive';
|
import { PasswordToggleDirective } from '@app/auth/_directives/password-toggle.directive';
|
||||||
import {MatCardModule} from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {MatRippleModule} from '@angular/material/core';
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AuthComponent, PasswordToggleDirective],
|
declarations: [AuthComponent, PasswordToggleDirective],
|
||||||
@ -23,6 +22,6 @@ import {MatRippleModule} from '@angular/material/core';
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatRippleModule,
|
MatRippleModule,
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule { }
|
export class AuthModule {}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
||||||
<li class="breadcrumb-item"><a routerLink="/accounts">Accounts</a></li>
|
<li class="breadcrumb-item"><a routerLink="/accounts">Accounts</a></li>
|
||||||
<li *ngIf="account !== undefined" class="breadcrumb-item active" aria-current="page">{{account?.vcard?.fn[0].value}}</li>
|
<li *ngIf="account" class="breadcrumb-item active" aria-current="page">{{account?.vcard?.fn[0].value}}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div *ngIf="!account" class="text-center">
|
<div *ngIf="!account" class="text-center">
|
||||||
@ -35,7 +35,10 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
|
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
|
||||||
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | date}}</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"> {{account?.identities.evm['bloxberg:8996']}} </a></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">
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="account" class="card mt-3 mb-3">
|
<div *ngIf="account" class="card mt-3 mb-3">
|
||||||
@ -75,11 +78,9 @@
|
|||||||
<mat-label> ACCOUNT TYPE: </mat-label>
|
<mat-label> ACCOUNT TYPE: </mat-label>
|
||||||
<mat-select id="accountType" [(value)]="account.type" formControlName="type"
|
<mat-select id="accountType" [(value)]="account.type" formControlName="type"
|
||||||
[errorStateMatcher]="matcher">
|
[errorStateMatcher]="matcher">
|
||||||
<mat-option value="user"> USER </mat-option>
|
<mat-option *ngFor="let accountType of accountTypes" [value]="accountType">
|
||||||
<mat-option value="cashier"> CASHIER </mat-option>
|
{{accountType | uppercase}}
|
||||||
<mat-option value="vendor"> VENDOR </mat-option>
|
</mat-option>
|
||||||
<mat-option value="tokenAgent"> TOKENAGENT </mat-option>
|
|
||||||
<mat-option value="group"> GROUPACCOUNT </mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.type.errors">Type is required.</mat-error>
|
<mat-error *ngIf="submitted && accountInfoFormStub.type.errors">Type is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -99,9 +100,9 @@
|
|||||||
<mat-label> GENDER: </mat-label>
|
<mat-label> GENDER: </mat-label>
|
||||||
<mat-select id="gender" [(value)]="account.gender" formControlName="gender"
|
<mat-select id="gender" [(value)]="account.gender" formControlName="gender"
|
||||||
[errorStateMatcher]="matcher">
|
[errorStateMatcher]="matcher">
|
||||||
<mat-option value="male"> MALE </mat-option>
|
<mat-option *ngFor="let gender of genders" [value]="gender">
|
||||||
<mat-option value="female"> FEMALE </mat-option>
|
{{gender | uppercase}}
|
||||||
<mat-option value="other"> OTHER </mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.gender.errors">Gender is required.</mat-error>
|
<mat-error *ngIf="submitted && accountInfoFormStub.gender.errors">Gender is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -112,16 +113,9 @@
|
|||||||
<mat-label> BUSINESS CATEGORY: </mat-label>
|
<mat-label> BUSINESS CATEGORY: </mat-label>
|
||||||
<mat-select id="businessCategory" [(value)]="account.category" formControlName="businessCategory"
|
<mat-select id="businessCategory" [(value)]="account.category" formControlName="businessCategory"
|
||||||
[errorStateMatcher]="matcher">
|
[errorStateMatcher]="matcher">
|
||||||
<mat-option value="food/water">Food/Water</mat-option>
|
<mat-option *ngFor="let category of categories" [value]="category">
|
||||||
<mat-option value="fuel/energy">Fuel/Energy</mat-option>
|
{{category | titlecase}}
|
||||||
<mat-option value="education">Education</mat-option>
|
</mat-option>
|
||||||
<mat-option value="health">Health</mat-option>
|
|
||||||
<mat-option value="shop">Shop</mat-option>
|
|
||||||
<mat-option value="environment">Environment</mat-option>
|
|
||||||
<mat-option value="transport">Transport</mat-option>
|
|
||||||
<mat-option value="farming/labour">Farming/Labour</mat-option>
|
|
||||||
<mat-option value="savings">Savings Group</mat-option>
|
|
||||||
<mat-option value="other">Savings Group</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.businessCategory.errors">
|
<mat-error *ngIf="submitted && accountInfoFormStub.businessCategory.errors">
|
||||||
Category is required.
|
Category is required.
|
||||||
@ -146,16 +140,9 @@
|
|||||||
<mat-label> LOCATION: </mat-label>
|
<mat-label> LOCATION: </mat-label>
|
||||||
<mat-select id="location" [(value)]="account.location.area" formControlName="location"
|
<mat-select id="location" [(value)]="account.location.area" formControlName="location"
|
||||||
[errorStateMatcher]="matcher">
|
[errorStateMatcher]="matcher">
|
||||||
<div *ngFor="let county of locations; trackBy: trackByName">
|
<mat-option *ngFor="let area of areaNames" [value]="area">
|
||||||
<div *ngFor="let district of county.districts; trackBy: trackByName">
|
{{area | uppercase}}
|
||||||
<mat-optgroup *ngFor="let location of district.locations; trackBy: trackByName" [label]="county.name + ' / ' +
|
</mat-option>
|
||||||
district.name + ' / ' + location.name">
|
|
||||||
<mat-option *ngFor="let village of location.villages; trackBy: trackByName" [value]="village">
|
|
||||||
{{village}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-optgroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.location.errors">Location is required.</mat-error>
|
<mat-error *ngIf="submitted && accountInfoFormStub.location.errors">Location is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
@ -166,23 +153,22 @@
|
|||||||
<mat-label> LOCATION TYPE: </mat-label>
|
<mat-label> LOCATION TYPE: </mat-label>
|
||||||
<mat-select id="locationType" [(value)]="account.location.area_type" formControlName="locationType"
|
<mat-select id="locationType" [(value)]="account.location.area_type" formControlName="locationType"
|
||||||
[errorStateMatcher]="matcher">
|
[errorStateMatcher]="matcher">
|
||||||
<mat-option value="Urban"> URBAN </mat-option>
|
<mat-option *ngFor="let type of areaTypes" [value]="type">
|
||||||
<mat-option value="Periurban"> PERIURBAN </mat-option>
|
{{type | uppercase}}
|
||||||
<mat-option value="Rural"> RURAL </mat-option>
|
</mat-option>
|
||||||
<mat-option value="Other"> OTHER </mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.locationType.errors">Location Type is required.</mat-error>
|
<mat-error *ngIf="submitted && accountInfoFormStub.locationType.errors">Location Type is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 col-lg-4">
|
<div class="col-md-6 col-lg-4">
|
||||||
<button mat-raised-button color="primary" type="button" class="btn btn btn-outline-primary mb-3">
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary mb-3">
|
||||||
Add User KYC
|
Add User KYC
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 col-lg-4">
|
<div class="col-md-6 col-lg-4">
|
||||||
<button mat-raised-button color="primary" type="button" class="btn btn btn-outline-success mb-3"
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-success mb-3"
|
||||||
(click)="resetPin()">
|
(click)="resetPin()">
|
||||||
Reset Pin
|
Reset Pin
|
||||||
</button>
|
</button>
|
||||||
@ -240,7 +226,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-tab-group dynamicHeight mat-align-tabs="start">
|
<mat-tab-group *ngIf="account" dynamicHeight mat-align-tabs="start">
|
||||||
<mat-tab label="Transactions">
|
<mat-tab label="Transactions">
|
||||||
<app-transaction-details [transaction]="transaction"></app-transaction-details>
|
<app-transaction-details [transaction]="transaction"></app-transaction-details>
|
||||||
<div class="card mt-1">
|
<div class="card mt-1">
|
||||||
@ -250,11 +236,9 @@
|
|||||||
<mat-label> TRANSACTION TYPE </mat-label>
|
<mat-label> TRANSACTION TYPE </mat-label>
|
||||||
<mat-select id="transferSelect" [(value)]="transactionsType" (selectionChange)="filterTransactions()">
|
<mat-select id="transferSelect" [(value)]="transactionsType" (selectionChange)="filterTransactions()">
|
||||||
<mat-option value="all">ALL TRANSFERS</mat-option>
|
<mat-option value="all">ALL TRANSFERS</mat-option>
|
||||||
<mat-option value="transaction">PAYMENTS</mat-option>
|
<mat-option *ngFor="let transactionType of transactionsTypes" [value]="transactionType">
|
||||||
<mat-option value="conversion">CONVERSION</mat-option>
|
{{transactionType | uppercase}}
|
||||||
<mat-option value="disbursements">DISBURSEMENTS</mat-option>
|
</mat-option>
|
||||||
<mat-option value="rewards">REWARDS</mat-option>
|
|
||||||
<mat-option value="reclamation">RECLAMATION</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(transactions, 'transactions')"> EXPORT </button>
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(transactions, 'transactions')"> EXPORT </button>
|
||||||
@ -324,11 +308,9 @@
|
|||||||
<mat-label> ACCOUNT TYPE </mat-label>
|
<mat-label> ACCOUNT TYPE </mat-label>
|
||||||
<mat-select id="typeSelect" [(value)]="accountsType" (selectionChange)="filterAccounts()">
|
<mat-select id="typeSelect" [(value)]="accountsType" (selectionChange)="filterAccounts()">
|
||||||
<mat-option value="all">ALL</mat-option>
|
<mat-option value="all">ALL</mat-option>
|
||||||
<mat-option value="user">USER</mat-option>
|
<mat-option *ngFor="let accountType of accountTypes" [value]="accountType">
|
||||||
<mat-option value="cashier">CASHIER</mat-option>
|
{{accountType | uppercase}}
|
||||||
<mat-option value="vendor">VENDOR</mat-option>
|
</mat-option>
|
||||||
<mat-option value="tokenAgent">TOKENAGENT</mat-option>
|
|
||||||
<mat-option value="group">GROUPACCOUNT</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(accounts, 'accounts')"> EXPORT </button>
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(accounts, 'accounts')"> EXPORT </button>
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
|
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import {AccountsModule} from '@pages/accounts/accounts.module';
|
import { AccountsModule } from '@pages/accounts/accounts.module';
|
||||||
import {UserService} from '@app/_services';
|
import { UserService } from '@app/_services';
|
||||||
import {AppModule} from '@app/app.module';
|
import { AppModule } from '@app/app.module';
|
||||||
import {ActivatedRouteStub, FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
|
import {
|
||||||
|
ActivatedRouteStub,
|
||||||
|
FooterStubComponent,
|
||||||
|
SidebarStubComponent,
|
||||||
|
TopbarStubComponent,
|
||||||
|
UserServiceStub,
|
||||||
|
} from '@src/testing';
|
||||||
|
|
||||||
describe('AccountDetailsComponent', () => {
|
describe('AccountDetailsComponent', () => {
|
||||||
let component: AccountDetailsComponent;
|
let component: AccountDetailsComponent;
|
||||||
@ -24,19 +30,14 @@ describe('AccountDetailsComponent', () => {
|
|||||||
AccountDetailsComponent,
|
AccountDetailsComponent,
|
||||||
FooterStubComponent,
|
FooterStubComponent,
|
||||||
SidebarStubComponent,
|
SidebarStubComponent,
|
||||||
TopbarStubComponent
|
TopbarStubComponent,
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
AccountsModule,
|
|
||||||
AppModule,
|
|
||||||
HttpClientTestingModule,
|
|
||||||
],
|
],
|
||||||
|
imports: [AccountsModule, AppModule, HttpClientTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: route },
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
{ provide: UserService, useClass: UserServiceStub }
|
{ provide: UserService, useClass: UserServiceStub },
|
||||||
]
|
],
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
httpClient = TestBed.inject(HttpClient);
|
httpClient = TestBed.inject(HttpClient);
|
||||||
httpTestingController = TestBed.inject(HttpTestingController);
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
});
|
});
|
||||||
@ -50,12 +51,4 @@ describe('AccountDetailsComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#addTransfer() should toggle #isDisbursing', () => {
|
|
||||||
expect(component.isDisbursing).toBe(false, 'off at first');
|
|
||||||
component.addTransfer();
|
|
||||||
expect(component.isDisbursing).toBe(true, 'on after click');
|
|
||||||
component.addTransfer();
|
|
||||||
expect(component.isDisbursing).toBe(false, 'off after second click');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,49 +1,67 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
|
import {
|
||||||
import {MatTableDataSource} from '@angular/material/table';
|
ChangeDetectionStrategy,
|
||||||
import {MatPaginator} from '@angular/material/paginator';
|
ChangeDetectorRef,
|
||||||
import {MatSort} from '@angular/material/sort';
|
Component,
|
||||||
import {BlockSyncService, LocationService, LoggingService, TokenService, TransactionService, UserService} from '@app/_services';
|
OnInit,
|
||||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
ViewChild,
|
||||||
import {first} from 'rxjs/operators';
|
} from '@angular/core';
|
||||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import {CustomErrorStateMatcher, exportCsv} from '@app/_helpers';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import {Envelope, User} from 'cic-client-meta';
|
import { MatSort } from '@angular/material/sort';
|
||||||
const vCard = require('vcard-parser');
|
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';
|
||||||
|
import { copyToClipboard, CustomErrorStateMatcher, exportCsv } from '@app/_helpers';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { add0x, strip0x } from '@src/assets/js/ethtx/dist/hex';
|
||||||
|
import { environment } from '@src/environments/environment';
|
||||||
|
import { AccountDetails, AreaName, AreaType, Category, Transaction } from '@app/_models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-details',
|
selector: 'app-account-details',
|
||||||
templateUrl: './account-details.component.html',
|
templateUrl: './account-details.component.html',
|
||||||
styleUrls: ['./account-details.component.scss'],
|
styleUrls: ['./account-details.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccountDetailsComponent implements OnInit {
|
export class AccountDetailsComponent implements OnInit {
|
||||||
transactionsDataSource: MatTableDataSource<any>;
|
transactionsDataSource: MatTableDataSource<any>;
|
||||||
transactionsDisplayedColumns = ['sender', 'recipient', 'value', 'created', 'type'];
|
transactionsDisplayedColumns: Array<string> = ['sender', 'recipient', 'value', 'created', 'type'];
|
||||||
transactionsDefaultPageSize = 10;
|
transactionsDefaultPageSize: number = 10;
|
||||||
transactionsPageSizeOptions = [10, 20, 50, 100];
|
transactionsPageSizeOptions: Array<number> = [10, 20, 50, 100];
|
||||||
@ViewChild('TransactionTablePaginator', {static: true}) transactionTablePaginator: MatPaginator;
|
@ViewChild('TransactionTablePaginator', { static: true }) transactionTablePaginator: MatPaginator;
|
||||||
@ViewChild('TransactionTableSort', {static: true}) transactionTableSort: MatSort;
|
@ViewChild('TransactionTableSort', { static: true }) transactionTableSort: MatSort;
|
||||||
|
|
||||||
userDataSource: MatTableDataSource<any>;
|
userDataSource: MatTableDataSource<any>;
|
||||||
userDisplayedColumns = ['name', 'phone', 'created', 'balance', 'location'];
|
userDisplayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location'];
|
||||||
usersDefaultPageSize = 10;
|
usersDefaultPageSize: number = 10;
|
||||||
usersPageSizeOptions = [10, 20, 50, 100];
|
usersPageSizeOptions: Array<number> = [10, 20, 50, 100];
|
||||||
@ViewChild('UserTablePaginator', {static: true}) userTablePaginator: MatPaginator;
|
@ViewChild('UserTablePaginator', { static: true }) userTablePaginator: MatPaginator;
|
||||||
@ViewChild('UserTableSort', {static: true}) userTableSort: MatSort;
|
@ViewChild('UserTableSort', { static: true }) userTableSort: MatSort;
|
||||||
|
|
||||||
accountInfoForm: FormGroup;
|
accountInfoForm: FormGroup;
|
||||||
account: any;
|
account: AccountDetails;
|
||||||
accountAddress: string;
|
accountAddress: string;
|
||||||
accountBalance: number;
|
|
||||||
accountStatus: any;
|
accountStatus: any;
|
||||||
metaAccount: any;
|
accounts: Array<AccountDetails> = [];
|
||||||
accounts: any[] = [];
|
accountsType: string = 'all';
|
||||||
accountsType = 'all';
|
categories: Array<Category>;
|
||||||
locations: any;
|
areaNames: Array<AreaName>;
|
||||||
|
areaTypes: Array<AreaType>;
|
||||||
transaction: any;
|
transaction: any;
|
||||||
transactions: any[];
|
transactions: Array<Transaction>;
|
||||||
transactionsType = 'all';
|
transactionsType: string = 'all';
|
||||||
matcher = new CustomErrorStateMatcher();
|
accountTypes: Array<string>;
|
||||||
|
transactionsTypes: Array<string>;
|
||||||
|
genders: Array<string>;
|
||||||
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
submitted: boolean = false;
|
submitted: boolean = false;
|
||||||
bloxbergLink: string;
|
bloxbergLink: string;
|
||||||
|
|
||||||
@ -56,7 +74,9 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private blockSyncService: BlockSyncService
|
private blockSyncService: BlockSyncService,
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
) {
|
) {
|
||||||
this.accountInfoForm = this.formBuilder.group({
|
this.accountInfoForm = this.formBuilder.group({
|
||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
@ -71,46 +91,71 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
locationType: ['', Validators.required],
|
locationType: ['', Validators.required],
|
||||||
});
|
});
|
||||||
this.route.paramMap.subscribe(async (params: Params) => {
|
this.route.paramMap.subscribe(async (params: Params) => {
|
||||||
this.accountAddress = params.get('id');
|
this.accountAddress = add0x(params.get('id'));
|
||||||
this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
|
this.bloxbergLink =
|
||||||
this.userService.getAccountDetailsFromMeta(await User.toKey(this.accountAddress)).pipe(first()).subscribe(async res => {
|
'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
|
||||||
this.metaAccount = Envelope.fromJSON(JSON.stringify(res.body)).unwrap();
|
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
|
||||||
this.account = this.metaAccount.m.data;
|
async (res) => {
|
||||||
this.loggingService.sendInfoLevelMessage(this.account);
|
if (res !== undefined) {
|
||||||
this.accountBalance = await this.tokenService.getTokenBalance(this.accountAddress);
|
this.account = res;
|
||||||
this.account.vcard = vCard.parse(atob(this.account.vcard));
|
this.cdr.detectChanges();
|
||||||
this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first()).subscribe(response => this.accountStatus = response);
|
this.loggingService.sendInfoLevelMessage(this.account);
|
||||||
this.accountInfoForm.patchValue({
|
// this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first())
|
||||||
name: this.account.vcard?.fn[0].value,
|
// .subscribe(response => this.accountStatus = response);
|
||||||
phoneNumber: this.account.vcard?.tel[0].value,
|
this.accountInfoForm.patchValue({
|
||||||
age: this.account.age,
|
name: this.account.vcard?.fn[0].value,
|
||||||
type: this.account.type,
|
phoneNumber: this.account.vcard?.tel[0].value,
|
||||||
bio: this.account.products,
|
age: this.account.age,
|
||||||
gender: this.account.gender,
|
type: this.account.type,
|
||||||
businessCategory: this.account.category,
|
bio: this.account.products,
|
||||||
userLocation: this.account.location.area_name,
|
gender: this.account.gender,
|
||||||
location: this.account.location.area,
|
businessCategory: this.account.category,
|
||||||
locationType: this.account.location.area_type,
|
userLocation: this.account.location.area_name,
|
||||||
});
|
location: this.account.location.area,
|
||||||
});
|
locationType: this.account.location.area_type,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Account not found!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
this.blockSyncService.blockSync(this.accountAddress);
|
this.blockSyncService.blockSync(this.accountAddress);
|
||||||
});
|
});
|
||||||
this.userService.getAccounts();
|
this.userService
|
||||||
this.locationService.getLocations();
|
.getCategories()
|
||||||
this.locationService.locationsSubject.subscribe(locations => {
|
.pipe(first())
|
||||||
this.locations = locations;
|
.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));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.userService.accountsSubject.subscribe(accounts => {
|
this.userService.accountsSubject.subscribe((accounts) => {
|
||||||
this.userDataSource = new MatTableDataSource<any>(accounts);
|
this.userDataSource = new MatTableDataSource<any>(accounts);
|
||||||
this.userDataSource.paginator = this.userTablePaginator;
|
this.userDataSource.paginator = this.userTablePaginator;
|
||||||
this.userDataSource.sort = this.userTableSort;
|
this.userDataSource.sort = this.userTableSort;
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.transactionService.transactionsSubject.subscribe(transactions => {
|
this.transactionService.transactionsSubject.subscribe((transactions) => {
|
||||||
this.transactionsDataSource = new MatTableDataSource<any>(transactions);
|
this.transactionsDataSource = new MatTableDataSource<any>(transactions);
|
||||||
this.transactionsDataSource.paginator = this.transactionTablePaginator;
|
this.transactionsDataSource.paginator = this.transactionTablePaginator;
|
||||||
this.transactionsDataSource.sort = this.transactionTableSort;
|
this.transactionsDataSource.sort = this.transactionTableSort;
|
||||||
@ -131,16 +176,22 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewAccount(account): void {
|
viewAccount(account): void {
|
||||||
this.router.navigateByUrl(`/accounts/${account.id}`);
|
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> {
|
async saveInfo(): Promise<void> {
|
||||||
this.submitted = true;
|
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(
|
const accountKey = await this.userService.changeAccountInfo(
|
||||||
this.account.address,
|
this.accountAddress,
|
||||||
this.accountInfoFormStub.name.value,
|
this.accountInfoFormStub.name.value,
|
||||||
this.accountInfoFormStub.phoneNumber.value,
|
this.accountInfoFormStub.phoneNumber.value,
|
||||||
this.accountInfoFormStub.age.value,
|
this.accountInfoFormStub.age.value,
|
||||||
@ -150,47 +201,58 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
this.accountInfoFormStub.businessCategory.value,
|
this.accountInfoFormStub.businessCategory.value,
|
||||||
this.accountInfoFormStub.userLocation.value,
|
this.accountInfoFormStub.userLocation.value,
|
||||||
this.accountInfoFormStub.location.value,
|
this.accountInfoFormStub.location.value,
|
||||||
this.accountInfoFormStub.locationType.value,
|
this.accountInfoFormStub.locationType.value
|
||||||
this.metaAccount
|
|
||||||
);
|
);
|
||||||
this.loggingService.sendInfoLevelMessage(`Response: ${accountKey}`);
|
|
||||||
this.submitted = false;
|
this.submitted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filterAccounts(): void {
|
filterAccounts(): void {
|
||||||
if (this.accountsType === 'all') {
|
if (this.accountsType === 'all') {
|
||||||
this.userService.accountsSubject.subscribe(accounts => {
|
this.userService.accountsSubject.subscribe((accounts) => {
|
||||||
this.userDataSource.data = accounts;
|
this.userDataSource.data = accounts;
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.userDataSource.data = this.accounts.filter(account => account.type === this.accountsType);
|
this.userDataSource.data = this.accounts.filter(
|
||||||
|
(account) => account.type === this.accountsType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filterTransactions(): void {
|
filterTransactions(): void {
|
||||||
if (this.transactionsType === 'all') {
|
if (this.transactionsType === 'all') {
|
||||||
this.transactionService.transactionsSubject.subscribe(transactions => {
|
this.transactionService.transactionsSubject.subscribe((transactions) => {
|
||||||
this.transactionsDataSource.data = transactions;
|
this.transactionsDataSource.data = transactions;
|
||||||
this.transactions = transactions;
|
this.transactions = transactions;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.transactionsDataSource.data = this.transactions.filter(transaction => transaction.type === this.transactionsType);
|
this.transactionsDataSource.data = this.transactions.filter(
|
||||||
|
(transaction) => transaction.type === this.transactionsType
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPin(): void {
|
resetPin(): void {
|
||||||
if (!confirm('Reset user\'s pin?')) { return; }
|
if (!confirm(`Reset user's pin?`)) {
|
||||||
this.userService.resetPin(this.account.phone).pipe(first()).subscribe(res => {
|
return;
|
||||||
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
}
|
||||||
});
|
this.userService
|
||||||
}
|
.resetPin(this.account.vcard.tel[0].value)
|
||||||
|
.pipe(first())
|
||||||
public trackByName(index, item): string {
|
.subscribe((res) => {
|
||||||
return item.name;
|
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadCsv(data: any, filename: string): void {
|
downloadCsv(data: any, filename: string): void {
|
||||||
exportCsv(data, filename);
|
exportCsv(data, filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copyAddress(): void {
|
||||||
|
if (copyToClipboard(this.accountAddress)) {
|
||||||
|
this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', {
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<!-- 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" appMenuSelection>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
||||||
|
<li class="breadcrumb-item"><a routerLink="/accounts">Accounts</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">Search</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<div class="card">
|
||||||
|
<mat-card-title class="card-header">
|
||||||
|
Accounts
|
||||||
|
</mat-card-title>
|
||||||
|
<div class="card-body">
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Phone Number">
|
||||||
|
<form [formGroup]="phoneSearchForm" (ngSubmit)="onPhoneSearch()">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label> Search </mat-label>
|
||||||
|
<input matInput type="text" placeholder="Search by phone number" formControlName="phoneNumber" [errorStateMatcher]="matcher">
|
||||||
|
<mat-error *ngIf="phoneSearchSubmitted && phoneSearchFormStub.phoneNumber.errors">Phone Number is required.</mat-error>
|
||||||
|
<mat-icon matSuffix>phone</mat-icon>
|
||||||
|
<mat-hint>Phone Number</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
<button mat-raised-button color="primary" type="submit" class="btn btn-outline-primary ml-3"> SEARCH </button>
|
||||||
|
</form>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Account Address">
|
||||||
|
<form [formGroup]="addressSearchForm" (ngSubmit)="onAddressSearch()">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label> Search </mat-label>
|
||||||
|
<input matInput type="text" placeholder="Search by account address" formControlName="address" [errorStateMatcher]="matcher">
|
||||||
|
<mat-error *ngIf="addressSearchSubmitted && addressSearchFormStub.address.errors">Account Address is required.</mat-error>
|
||||||
|
<mat-icon matSuffix>view_in_ar</mat-icon>
|
||||||
|
<mat-hint>Account Address</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
<button mat-raised-button color="primary" type="submit" class="btn btn-outline-primary ml-3"> SEARCH </button>
|
||||||
|
</form>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<app-footer appMenuSelection></app-footer>
|
||||||
|
</div>
|
||||||
|
<!-- ============================================================== -->
|
||||||
|
<!-- End Page content -->
|
||||||
|
<!-- ============================================================== -->
|
||||||
|
</div>
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AccountSearchComponent } from './account-search.component';
|
||||||
|
|
||||||
|
describe('AccountSearchComponent', () => {
|
||||||
|
let component: AccountSearchComponent;
|
||||||
|
let fixture: ComponentFixture<AccountSearchComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [AccountSearchComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AccountSearchComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,104 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||||
|
import { UserService } from '@app/_services';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { strip0x } from '@src/assets/js/ethtx/dist/hex';
|
||||||
|
import { environment } from '@src/environments/environment';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-account-search',
|
||||||
|
templateUrl: './account-search.component.html',
|
||||||
|
styleUrls: ['./account-search.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class AccountSearchComponent implements OnInit {
|
||||||
|
nameSearchForm: FormGroup;
|
||||||
|
nameSearchSubmitted: boolean = false;
|
||||||
|
nameSearchLoading: boolean = false;
|
||||||
|
phoneSearchForm: FormGroup;
|
||||||
|
phoneSearchSubmitted: boolean = false;
|
||||||
|
phoneSearchLoading: boolean = false;
|
||||||
|
addressSearchForm: FormGroup;
|
||||||
|
addressSearchSubmitted: boolean = false;
|
||||||
|
addressSearchLoading: boolean = false;
|
||||||
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private userService: UserService,
|
||||||
|
private router: Router
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.nameSearchForm = this.formBuilder.group({
|
||||||
|
name: ['', Validators.required],
|
||||||
|
});
|
||||||
|
this.phoneSearchForm = this.formBuilder.group({
|
||||||
|
phoneNumber: ['', Validators.required],
|
||||||
|
});
|
||||||
|
this.addressSearchForm = this.formBuilder.group({
|
||||||
|
address: ['', Validators.required],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
this.nameSearchLoading = true;
|
||||||
|
this.userService.searchAccountByName(this.nameSearchFormStub.name.value);
|
||||||
|
this.nameSearchLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPhoneSearch(): Promise<void> {
|
||||||
|
this.phoneSearchSubmitted = true;
|
||||||
|
if (this.phoneSearchForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.phoneSearchLoading = true;
|
||||||
|
(
|
||||||
|
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])}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
alert('Account not found!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.phoneSearchLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onAddressSearch(): Promise<void> {
|
||||||
|
this.addressSearchSubmitted = true;
|
||||||
|
if (this.addressSearchForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.addressSearchLoading = true;
|
||||||
|
(
|
||||||
|
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])}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
alert('Account not found!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.addressSearchLoading = false;
|
||||||
|
}
|
||||||
|
}
|
@ -2,20 +2,20 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { AccountsComponent } from '@pages/accounts/accounts.component';
|
import { AccountsComponent } from '@pages/accounts/accounts.component';
|
||||||
import {CreateAccountComponent} from '@pages/accounts/create-account/create-account.component';
|
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
|
||||||
import {ExportAccountsComponent} from '@pages/accounts/export-accounts/export-accounts.component';
|
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
|
||||||
import {AccountDetailsComponent} from '@pages/accounts/account-details/account-details.component';
|
import { AccountSearchComponent } from '@pages/accounts/account-search/account-search.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: AccountsComponent },
|
{ path: '', component: AccountsComponent },
|
||||||
|
{ path: 'search', component: AccountSearchComponent },
|
||||||
// { path: 'create', component: CreateAccountComponent },
|
// { path: 'create', component: CreateAccountComponent },
|
||||||
{ path: 'export', component: ExportAccountsComponent },
|
|
||||||
{ path: ':id', component: AccountDetailsComponent },
|
{ path: ':id', component: AccountDetailsComponent },
|
||||||
{ path: '**', redirectTo: '', pathMatch: 'full' }
|
{ path: '**', redirectTo: '', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AccountsRoutingModule { }
|
export class AccountsRoutingModule {}
|
||||||
|
@ -26,14 +26,13 @@
|
|||||||
<mat-label> ACCOUNT TYPE </mat-label>
|
<mat-label> ACCOUNT TYPE </mat-label>
|
||||||
<mat-select id="typeSelect" [(value)]="accountsType" (selectionChange)="filterAccounts()">
|
<mat-select id="typeSelect" [(value)]="accountsType" (selectionChange)="filterAccounts()">
|
||||||
<mat-option value="all">ALL</mat-option>
|
<mat-option value="all">ALL</mat-option>
|
||||||
<mat-option value="user">USER</mat-option>
|
<mat-option *ngFor="let accountType of accountTypes" [value]="accountType">
|
||||||
<mat-option value="cashier">CASHIER</mat-option>
|
{{accountType | uppercase}}
|
||||||
<mat-option value="vendor">VENDOR</mat-option>
|
</mat-option>
|
||||||
<mat-option value="tokenAgent">TOKENAGENT</mat-option>
|
|
||||||
<mat-option value="group">GROUPACCOUNT</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" routerLink="/accounts/search"> SEARCH </button>
|
||||||
|
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AccountsComponent } from './accounts.component';
|
import { AccountsComponent } from './accounts.component';
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
|
import {
|
||||||
import {AccountsModule} from '@pages/accounts/accounts.module';
|
FooterStubComponent,
|
||||||
import {AppModule} from '@app/app.module';
|
SidebarStubComponent,
|
||||||
import {HttpClient} from '@angular/common/http';
|
TopbarStubComponent,
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
UserServiceStub,
|
||||||
import {UserService} from '@app/_services';
|
} from '@src/testing';
|
||||||
|
import { AccountsModule } from '@pages/accounts/accounts.module';
|
||||||
|
import { AppModule } from '@app/app.module';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
|
import { UserService } from '@app/_services';
|
||||||
|
|
||||||
describe('AccountsComponent', () => {
|
describe('AccountsComponent', () => {
|
||||||
let component: AccountsComponent;
|
let component: AccountsComponent;
|
||||||
@ -20,18 +25,11 @@ describe('AccountsComponent', () => {
|
|||||||
AccountsComponent,
|
AccountsComponent,
|
||||||
FooterStubComponent,
|
FooterStubComponent,
|
||||||
SidebarStubComponent,
|
SidebarStubComponent,
|
||||||
TopbarStubComponent
|
TopbarStubComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [AccountsModule, AppModule, HttpClientTestingModule],
|
||||||
AccountsModule,
|
providers: [{ provide: UserService, useClass: UserServiceStub }],
|
||||||
AppModule,
|
}).compileComponents();
|
||||||
HttpClientTestingModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: UserService, useClass: UserServiceStub }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
httpClient = TestBed.inject(HttpClient);
|
httpClient = TestBed.inject(HttpClient);
|
||||||
httpTestingController = TestBed.inject(HttpTestingController);
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
});
|
});
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import {MatTableDataSource} from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import {MatPaginator} from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import {MatSort} from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import {LoggingService, UserService} from '@app/_services';
|
import { LoggingService, UserService } from '@app/_services';
|
||||||
import {Router} from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {exportCsv} from '@app/_helpers';
|
import { exportCsv } from '@app/_helpers';
|
||||||
|
import { strip0x } from '@src/assets/js/ethtx/dist/hex';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { environment } from '@src/environments/environment';
|
||||||
|
import { AccountDetails } from '@app/_models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-accounts',
|
selector: 'app-accounts',
|
||||||
templateUrl: './accounts.component.html',
|
templateUrl: './accounts.component.html',
|
||||||
styleUrls: ['./accounts.component.scss'],
|
styleUrls: ['./accounts.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccountsComponent implements OnInit {
|
export class AccountsComponent implements OnInit {
|
||||||
dataSource: MatTableDataSource<any>;
|
dataSource: MatTableDataSource<any>;
|
||||||
accounts: any[] = [];
|
accounts: Array<AccountDetails> = [];
|
||||||
displayedColumns = ['name', 'phone', 'created', 'balance', 'location'];
|
displayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location'];
|
||||||
defaultPageSize = 10;
|
defaultPageSize: number = 10;
|
||||||
pageSizeOptions = [10, 20, 50, 100];
|
pageSizeOptions: Array<number> = [10, 20, 50, 100];
|
||||||
accountsType = 'all';
|
accountsType: string = 'all';
|
||||||
|
accountTypes: Array<string>;
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
@ -30,15 +35,20 @@ export class AccountsComponent implements OnInit {
|
|||||||
) {
|
) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
// TODO it feels like this should be in the onInit handler
|
||||||
await this.userService.loadAccounts(100);
|
await this.userService.loadAccounts(100);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, {error});
|
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error });
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
this.userService
|
||||||
|
.getAccountTypes()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((res) => (this.accountTypes = res));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.userService.accountsSubject.subscribe(accounts => {
|
this.userService.accountsSubject.subscribe((accounts) => {
|
||||||
this.dataSource = new MatTableDataSource<any>(accounts);
|
this.dataSource = new MatTableDataSource<any>(accounts);
|
||||||
this.dataSource.paginator = this.paginator;
|
this.dataSource.paginator = this.paginator;
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
@ -51,17 +61,19 @@ export class AccountsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async viewAccount(account): Promise<void> {
|
async viewAccount(account): Promise<void> {
|
||||||
await this.router.navigateByUrl(`/accounts/${account.identities.evm['bloxberg:8996']}`);
|
await this.router.navigateByUrl(
|
||||||
|
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
filterAccounts(): void {
|
filterAccounts(): void {
|
||||||
if (this.accountsType === 'all') {
|
if (this.accountsType === 'all') {
|
||||||
this.userService.accountsSubject.subscribe(accounts => {
|
this.userService.accountsSubject.subscribe((accounts) => {
|
||||||
this.dataSource.data = accounts;
|
this.dataSource.data = accounts;
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.dataSource.data = this.accounts.filter(account => account.type === this.accountsType);
|
this.dataSource.data = this.accounts.filter((account) => account.type === this.accountsType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,36 +3,38 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module';
|
import { AccountsRoutingModule } from '@pages/accounts/accounts-routing.module';
|
||||||
import { AccountsComponent } from '@pages/accounts/accounts.component';
|
import { AccountsComponent } from '@pages/accounts/accounts.component';
|
||||||
import {SharedModule} from '@app/shared/shared.module';
|
import { SharedModule } from '@app/shared/shared.module';
|
||||||
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
|
import { AccountDetailsComponent } from '@pages/accounts/account-details/account-details.component';
|
||||||
import {DataTablesModule} from 'angular-datatables';
|
|
||||||
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
|
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
|
||||||
import { DisbursementComponent } from '@pages/accounts/disbursement/disbursement.component';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { ExportAccountsComponent } from '@pages/accounts/export-accounts/export-accounts.component';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import {MatTableModule} from '@angular/material/table';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
import {MatSortModule} from '@angular/material/sort';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import {MatPaginatorModule} from '@angular/material/paginator';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import {MatCardModule} from '@angular/material/card';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import { TransactionsModule } from '@pages/transactions/transactions.module';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
import { MatTabsModule } from '@angular/material/tabs';
|
||||||
import {TransactionsModule} from '@pages/transactions/transactions.module';
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
import {MatTabsModule} from '@angular/material/tabs';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import {MatRippleModule} from '@angular/material/core';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
|
import { AccountSearchComponent } from './account-search/account-search.component';
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AccountsComponent, AccountDetailsComponent, CreateAccountComponent, DisbursementComponent, ExportAccountsComponent],
|
declarations: [
|
||||||
|
AccountsComponent,
|
||||||
|
AccountDetailsComponent,
|
||||||
|
CreateAccountComponent,
|
||||||
|
AccountSearchComponent,
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
AccountsRoutingModule,
|
AccountsRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
DataTablesModule,
|
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatCheckboxModule,
|
MatCheckboxModule,
|
||||||
@ -47,7 +49,8 @@ import {ReactiveFormsModule} from '@angular/forms';
|
|||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatRippleModule,
|
MatRippleModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule,
|
||||||
]
|
MatSnackBarModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AccountsModule { }
|
export class AccountsModule {}
|
||||||
|
@ -27,11 +27,9 @@
|
|||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Account Type: </mat-label>
|
<mat-label>Account Type: </mat-label>
|
||||||
<mat-select id="accountType" formControlName="accountType" [errorStateMatcher]="matcher">
|
<mat-select id="accountType" formControlName="accountType" [errorStateMatcher]="matcher">
|
||||||
<mat-option value="user">USER</mat-option>
|
<mat-option *ngFor="let accountType of accountTypes" [value]="accountType">
|
||||||
<mat-option value="cashier">CASHIER</mat-option>
|
{{accountType | uppercase}}
|
||||||
<mat-option value="vendor">VENDOR</mat-option>
|
</mat-option>
|
||||||
<mat-option value="tokenAgent">TOKENAGENT</mat-option>
|
|
||||||
<mat-option value="group">GROUPACCOUNT</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && createFormStub.accountType.errors">Account type is required.</mat-error>
|
<mat-error *ngIf="submitted && createFormStub.accountType.errors">Account type is required.</mat-error>
|
||||||
</mat-form-field><br>
|
</mat-form-field><br>
|
||||||
@ -81,15 +79,9 @@
|
|||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Location: </mat-label>
|
<mat-label>Location: </mat-label>
|
||||||
<mat-select id="location" formControlName="location" [errorStateMatcher]="matcher">
|
<mat-select id="location" formControlName="location" [errorStateMatcher]="matcher">
|
||||||
<div *ngFor="let county of locations; trackBy: trackByName">
|
<mat-option *ngFor="let area of areaNames" [value]="area">
|
||||||
<div *ngFor="let district of county.districts; trackBy: trackByName">
|
{{area | uppercase}}
|
||||||
<mat-optgroup *ngFor="let location of district.locations; trackBy: trackByName" [label]="county.name + ' / ' + district.name + ' / ' + location.name">
|
</mat-option>
|
||||||
<mat-option *ngFor="let village of location.villages; trackBy: trackByName" [value]="village">
|
|
||||||
{{village}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-optgroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && createFormStub.location.errors">Location is required.</mat-error>
|
<mat-error *ngIf="submitted && createFormStub.location.errors">Location is required.</mat-error>
|
||||||
</mat-form-field><br>
|
</mat-form-field><br>
|
||||||
@ -99,9 +91,9 @@
|
|||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Gender: </mat-label>
|
<mat-label>Gender: </mat-label>
|
||||||
<mat-select id="gender" formControlName="gender" [errorStateMatcher]="matcher">
|
<mat-select id="gender" formControlName="gender" [errorStateMatcher]="matcher">
|
||||||
<mat-option value="female">FEMALE</mat-option>
|
<mat-option *ngFor="let gender of genders" [value]="gender">
|
||||||
<mat-option value="male">MALE</mat-option>
|
{{gender | uppercase}}
|
||||||
<mat-option value="other">OTHER</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && createFormStub.gender.errors">Gender is required.</mat-error>
|
<mat-error *ngIf="submitted && createFormStub.gender.errors">Gender is required.</mat-error>
|
||||||
</mat-form-field><br>
|
</mat-form-field><br>
|
||||||
@ -119,16 +111,9 @@
|
|||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Business Category: </mat-label>
|
<mat-label>Business Category: </mat-label>
|
||||||
<mat-select id="businessCategory" formControlName="businessCategory" [errorStateMatcher]="matcher">
|
<mat-select id="businessCategory" formControlName="businessCategory" [errorStateMatcher]="matcher">
|
||||||
<mat-option value="food/water">Food/Water</mat-option>
|
<mat-option *ngFor="let category of categories" [value]="category">
|
||||||
<mat-option value="fuel/energy">Fuel/Energy</mat-option>
|
{{category | titlecase}}
|
||||||
<mat-option value="education">Education</mat-option>
|
</mat-option>
|
||||||
<mat-option value="health">Health</mat-option>
|
|
||||||
<mat-option value="shop">Shop</mat-option>
|
|
||||||
<mat-option value="environment">Environment</mat-option>
|
|
||||||
<mat-option value="transport">Transport</mat-option>
|
|
||||||
<mat-option value="farming/labour">Farming/Labour</mat-option>
|
|
||||||
<mat-option value="savings">Savings Group</mat-option>
|
|
||||||
<mat-option value="other">Other</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<mat-error *ngIf="submitted && createFormStub.businessCategory.errors">Business Category is required.</mat-error>
|
<mat-error *ngIf="submitted && createFormStub.businessCategory.errors">Business Category is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
|
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
|
||||||
import {AccountsModule} from '@pages/accounts/accounts.module';
|
import { AccountsModule } from '@pages/accounts/accounts.module';
|
||||||
import {AppModule} from '@app/app.module';
|
import { AppModule } from '@app/app.module';
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
|
import { FooterStubComponent, SidebarStubComponent, TopbarStubComponent } from '@src/testing';
|
||||||
|
|
||||||
|
|
||||||
describe('CreateAccountComponent', () => {
|
describe('CreateAccountComponent', () => {
|
||||||
let component: CreateAccountComponent;
|
let component: CreateAccountComponent;
|
||||||
@ -16,14 +15,10 @@ describe('CreateAccountComponent', () => {
|
|||||||
CreateAccountComponent,
|
CreateAccountComponent,
|
||||||
FooterStubComponent,
|
FooterStubComponent,
|
||||||
SidebarStubComponent,
|
SidebarStubComponent,
|
||||||
TopbarStubComponent
|
TopbarStubComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [AccountsModule, AppModule],
|
||||||
AccountsModule,
|
}).compileComponents();
|
||||||
AppModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,24 +1,30 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import {LocationService} from '@app/_services';
|
import { LocationService, UserService } from '@app/_services';
|
||||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { AreaName, Category } from '@app/_models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-account',
|
selector: 'app-create-account',
|
||||||
templateUrl: './create-account.component.html',
|
templateUrl: './create-account.component.html',
|
||||||
styleUrls: ['./create-account.component.scss'],
|
styleUrls: ['./create-account.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class CreateAccountComponent implements OnInit {
|
export class CreateAccountComponent implements OnInit {
|
||||||
createForm: FormGroup;
|
createForm: FormGroup;
|
||||||
matcher = new CustomErrorStateMatcher();
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
submitted: boolean = false;
|
submitted: boolean = false;
|
||||||
locations: any;
|
categories: Array<Category>;
|
||||||
|
areaNames: Array<AreaName>;
|
||||||
|
accountTypes: Array<string>;
|
||||||
|
genders: Array<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private locationService: LocationService
|
private locationService: LocationService,
|
||||||
) { }
|
private userService: UserService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.createForm = this.formBuilder.group({
|
this.createForm = this.formBuilder.group({
|
||||||
@ -31,23 +37,35 @@ export class CreateAccountComponent implements OnInit {
|
|||||||
location: ['', Validators.required],
|
location: ['', Validators.required],
|
||||||
gender: ['', Validators.required],
|
gender: ['', Validators.required],
|
||||||
referrer: ['', Validators.required],
|
referrer: ['', Validators.required],
|
||||||
businessCategory: ['', Validators.required]
|
businessCategory: ['', Validators.required],
|
||||||
});
|
|
||||||
this.locationService.getLocations();
|
|
||||||
this.locationService.locationsSubject.subscribe(locations => {
|
|
||||||
this.locations = locations;
|
|
||||||
});
|
});
|
||||||
|
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 {
|
onSubmit(): void {
|
||||||
this.submitted = true;
|
this.submitted = true;
|
||||||
if (this.createForm.invalid || !confirm('Create account?')) { return; }
|
if (this.createForm.invalid || !confirm('Create account?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.submitted = false;
|
this.submitted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public trackByName(index, item): string {
|
|
||||||
return item.name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
<div class="card">
|
|
||||||
<mat-card-title class="card-header">
|
|
||||||
<div class="row">
|
|
||||||
NEW TRANSFER
|
|
||||||
<button mat-raised-button color="warn" type="button" class="btn btn-outline-danger ml-auto mr-2" (click)="cancel()"> CANCEL </button>
|
|
||||||
</div>
|
|
||||||
</mat-card-title>
|
|
||||||
<div class="card-body">
|
|
||||||
<form [formGroup]="disbursementForm" (ngSubmit)="createTransfer()">
|
|
||||||
<div class="row form-inline">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label> TRANSACTION TYPE </mat-label>
|
|
||||||
<mat-select id="transactionType" formControlName="transactionType" [errorStateMatcher]="matcher">
|
|
||||||
<mat-option value="disbursement">DISBURSEMENT</mat-option>
|
|
||||||
<mat-option value="transfer">TRANSFER</mat-option>
|
|
||||||
<mat-option value="deposit">DEPOSIT</mat-option>
|
|
||||||
<mat-option value="reclamation">RECLAMATION</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
<mat-error *ngIf="submitted && disbursementFormStub.transactionType.errors">Transaction type is required.</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field *ngIf="disbursementFormStub.transactionType.value === 'transfer'" appearance="outline" class="ml-3">
|
|
||||||
<mat-label>Enter Recipient: </mat-label>
|
|
||||||
<input matInput type="text" id="recipient" placeholder="Recipient" formControlName="recipient">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline" class="ml-3">
|
|
||||||
<mat-label>Enter Amount: </mat-label>
|
|
||||||
<input matInput type="text" id="amount" placeholder="Amount" formControlName="amount" [errorStateMatcher]="matcher">
|
|
||||||
<mat-error *ngIf="submitted && disbursementFormStub.amount.errors">Amount is required.</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
<button mat-raised-button color="primary" type="submit" class="btn btn-outline-primary ml-3" style="margin-bottom: 1.2rem;">
|
|
||||||
CREATE TRANSFER
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,37 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DisbursementComponent } from '@pages/accounts/disbursement/disbursement.component';
|
|
||||||
import {AccountsModule} from '@pages/accounts/accounts.module';
|
|
||||||
import {AppModule} from '@app/app.module';
|
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
|
|
||||||
|
|
||||||
describe('DisbursementComponent', () => {
|
|
||||||
let component: DisbursementComponent;
|
|
||||||
let fixture: ComponentFixture<DisbursementComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
DisbursementComponent,
|
|
||||||
FooterStubComponent,
|
|
||||||
SidebarStubComponent,
|
|
||||||
TopbarStubComponent
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
AccountsModule,
|
|
||||||
AppModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(DisbursementComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,51 +0,0 @@
|
|||||||
import {Component, OnInit, EventEmitter, Output, Input, ChangeDetectionStrategy} from '@angular/core';
|
|
||||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
|
||||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
|
||||||
import {TransactionService} from '@app/_services';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-disbursement',
|
|
||||||
templateUrl: './disbursement.component.html',
|
|
||||||
styleUrls: ['./disbursement.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class DisbursementComponent implements OnInit {
|
|
||||||
@Input() account;
|
|
||||||
@Output() cancelDisbursmentEvent = new EventEmitter();
|
|
||||||
disbursementForm: FormGroup;
|
|
||||||
matcher = new CustomErrorStateMatcher();
|
|
||||||
submitted: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private formBuilder: FormBuilder,
|
|
||||||
private transactionService: TransactionService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.disbursementForm = this.formBuilder.group({
|
|
||||||
transactionType: ['', Validators.required],
|
|
||||||
recipient: '',
|
|
||||||
amount: ['', Validators.required]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get disbursementFormStub(): any { return this.disbursementForm.controls; }
|
|
||||||
|
|
||||||
async createTransfer(): Promise<void> {
|
|
||||||
this.submitted = true;
|
|
||||||
if (this.disbursementForm.invalid || !confirm('Make transfer?')) { return; }
|
|
||||||
if (this.disbursementFormStub.transactionType.value === 'transfer') {
|
|
||||||
await this.transactionService.transferRequest(
|
|
||||||
this.account.token,
|
|
||||||
this.account.address,
|
|
||||||
this.disbursementFormStub.recipient.value,
|
|
||||||
this.disbursementFormStub.amount.value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.submitted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel(): void {
|
|
||||||
this.cancelDisbursmentEvent.emit();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
<!-- 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" appMenuSelection>
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
|
||||||
<li class="breadcrumb-item"><a routerLink="/accounts">Accounts</a></li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page">Export Accounts</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<div class="card">
|
|
||||||
<mat-card-title class="card-header">
|
|
||||||
EXPORT ACCOUNTS
|
|
||||||
</mat-card-title>
|
|
||||||
<div class="card-body">
|
|
||||||
<form [formGroup]="exportForm" (ngSubmit)="export()">
|
|
||||||
<div class="form-inline mb-2">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Export : </mat-label>
|
|
||||||
<mat-select id="accountType" formControlName="accountType" [errorStateMatcher]="matcher">
|
|
||||||
<mat-option value="vendors">VENDORS</mat-option>
|
|
||||||
<mat-option value="partners">PARTNERS</mat-option>
|
|
||||||
<mat-option value="selected">SELECTED</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
<mat-error *ngIf="submitted && exportFormStub.accountType.errors">Account Type is required.</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="form-inline mb-3">
|
|
||||||
<div class="form-group form-check">
|
|
||||||
<label class="form-check-label mr-2" for="transfers">Include transfers?</label>
|
|
||||||
<mat-checkbox id="transfers" formControlName="transfers"></mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button mat-raised-button color="primary" type="submit" class="btn btn-outline-primary"> EXPORT </button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<app-footer appMenuSelection></app-footer>
|
|
||||||
</div>
|
|
||||||
<!-- ============================================================== -->
|
|
||||||
<!-- End Page content -->
|
|
||||||
<!-- ============================================================== -->
|
|
||||||
</div>
|
|
@ -1,37 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ExportAccountsComponent } from '@pages/accounts/export-accounts/export-accounts.component';
|
|
||||||
import {AccountsModule} from '@pages/accounts/accounts.module';
|
|
||||||
import {AppModule} from '@app/app.module';
|
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent} from '@src/testing';
|
|
||||||
|
|
||||||
describe('ExportAccountsComponent', () => {
|
|
||||||
let component: ExportAccountsComponent;
|
|
||||||
let fixture: ComponentFixture<ExportAccountsComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
declarations: [
|
|
||||||
ExportAccountsComponent,
|
|
||||||
FooterStubComponent,
|
|
||||||
SidebarStubComponent,
|
|
||||||
TopbarStubComponent
|
|
||||||
],
|
|
||||||
imports: [
|
|
||||||
AccountsModule,
|
|
||||||
AppModule
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(ExportAccountsComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,34 +0,0 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
|
||||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
|
||||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-export-accounts',
|
|
||||||
templateUrl: './export-accounts.component.html',
|
|
||||||
styleUrls: ['./export-accounts.component.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
|
||||||
})
|
|
||||||
export class ExportAccountsComponent implements OnInit {
|
|
||||||
exportForm: FormGroup;
|
|
||||||
matcher = new CustomErrorStateMatcher();
|
|
||||||
submitted: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private formBuilder: FormBuilder
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.exportForm = this.formBuilder.group({
|
|
||||||
accountType: ['', Validators.required],
|
|
||||||
transfers: ['']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get exportFormStub(): any { return this.exportForm.controls; }
|
|
||||||
|
|
||||||
export(): void {
|
|
||||||
this.submitted = true;
|
|
||||||
if (this.exportForm.invalid || !confirm('Export accounts?')) { return; }
|
|
||||||
this.submitted = false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,6 +7,6 @@ const routes: Routes = [{ path: '', component: AdminComponent }];
|
|||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AdminRoutingModule { }
|
export class AdminRoutingModule {}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { AdminComponent } from '@pages/admin/admin.component';
|
import { AdminComponent } from '@pages/admin/admin.component';
|
||||||
import {HttpClient} from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
|
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||||
import {AdminModule} from '@pages/admin/admin.module';
|
import { AdminModule } from '@pages/admin/admin.module';
|
||||||
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing';
|
import {
|
||||||
import {AppModule} from '@app/app.module';
|
FooterStubComponent,
|
||||||
import {UserService} from '@app/_services';
|
SidebarStubComponent,
|
||||||
|
TopbarStubComponent,
|
||||||
|
UserServiceStub,
|
||||||
|
} from '@src/testing';
|
||||||
|
import { AppModule } from '@app/app.module';
|
||||||
|
import { UserService } from '@app/_services';
|
||||||
|
|
||||||
describe('AdminComponent', () => {
|
describe('AdminComponent', () => {
|
||||||
let component: AdminComponent;
|
let component: AdminComponent;
|
||||||
@ -21,18 +26,11 @@ describe('AdminComponent', () => {
|
|||||||
AdminComponent,
|
AdminComponent,
|
||||||
FooterStubComponent,
|
FooterStubComponent,
|
||||||
SidebarStubComponent,
|
SidebarStubComponent,
|
||||||
TopbarStubComponent
|
TopbarStubComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [AdminModule, AppModule, HttpClientTestingModule],
|
||||||
AdminModule,
|
providers: [{ provide: UserService, useClass: UserServiceStub }],
|
||||||
AppModule,
|
}).compileComponents();
|
||||||
HttpClientTestingModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
{ provide: UserService, useClass: UserServiceStub }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
httpClient = TestBed.inject(HttpClient);
|
httpClient = TestBed.inject(HttpClient);
|
||||||
httpTestingController = TestBed.inject(HttpTestingController);
|
httpTestingController = TestBed.inject(HttpTestingController);
|
||||||
userService = new UserServiceStub();
|
userService = new UserServiceStub();
|
||||||
@ -55,7 +53,7 @@ describe('AdminComponent', () => {
|
|||||||
user: 'Tom',
|
user: 'Tom',
|
||||||
role: 'enroller',
|
role: 'enroller',
|
||||||
action: 'Disburse RSV 100',
|
action: 'Disburse RSV 100',
|
||||||
approval: false
|
approval: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import {MatTableDataSource} from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import {MatPaginator} from '@angular/material/paginator';
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
import {MatSort} from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import {LoggingService, UserService} from '@app/_services';
|
import { LoggingService, UserService } from '@app/_services';
|
||||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
import {first} from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import {exportCsv} from '@app/_helpers';
|
import { exportCsv } from '@app/_helpers';
|
||||||
|
import { Action } from '../../_models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-admin',
|
selector: 'app-admin',
|
||||||
@ -14,37 +15,32 @@ import {exportCsv} from '@app/_helpers';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [
|
animations: [
|
||||||
trigger('detailExpand', [
|
trigger('detailExpand', [
|
||||||
state('collapsed', style({height: '0px', minHeight: 0, visibility: 'hidden'})),
|
state('collapsed', style({ height: '0px', minHeight: 0, visibility: 'hidden' })),
|
||||||
state('expanded', style({height: '*', visibility: 'visible'})),
|
state('expanded', style({ height: '*', visibility: 'visible' })),
|
||||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
|
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
|
||||||
])
|
]),
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AdminComponent implements OnInit {
|
export class AdminComponent implements OnInit {
|
||||||
dataSource: MatTableDataSource<any>;
|
dataSource: MatTableDataSource<any>;
|
||||||
displayedColumns = ['expand', 'user', 'role', 'action', 'status', 'approve'];
|
displayedColumns: Array<string> = ['expand', 'user', 'role', 'action', 'status', 'approve'];
|
||||||
action: any;
|
action: Action;
|
||||||
actions: any;
|
actions: Array<Action>;
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
constructor(
|
constructor(private userService: UserService, private loggingService: LoggingService) {
|
||||||
private userService: UserService,
|
|
||||||
private loggingService: LoggingService
|
|
||||||
) {
|
|
||||||
this.userService.getActions();
|
this.userService.getActions();
|
||||||
this.userService.actionsSubject.subscribe(actions => {
|
this.userService.actionsSubject.subscribe((actions) => {
|
||||||
this.dataSource = new MatTableDataSource<any>(actions);
|
this.dataSource = new MatTableDataSource<any>(actions);
|
||||||
this.dataSource.paginator = this.paginator;
|
this.dataSource.paginator = this.paginator;
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
this.actions = actions;
|
this.actions = actions;
|
||||||
console.log(this.actions);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {}
|
||||||
}
|
|
||||||
|
|
||||||
doFilter(value: string): void {
|
doFilter(value: string): void {
|
||||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||||
@ -55,14 +51,24 @@ export class AdminComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
approveAction(action: any): void {
|
approveAction(action: any): void {
|
||||||
if (!confirm('Approve action?')) { return; }
|
if (!confirm('Approve action?')) {
|
||||||
this.userService.approveAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
|
return;
|
||||||
|
}
|
||||||
|
this.userService
|
||||||
|
.approveAction(action.id)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
|
||||||
this.userService.getActions();
|
this.userService.getActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
disapproveAction(action: any): void {
|
disapproveAction(action: any): void {
|
||||||
if (!confirm('Disapprove action?')) { return; }
|
if (!confirm('Disapprove action?')) {
|
||||||
this.userService.revokeAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
|
return;
|
||||||
|
}
|
||||||
|
this.userService
|
||||||
|
.revokeAction(action.id)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((res) => this.loggingService.sendInfoLevelMessage(res));
|
||||||
this.userService.getActions();
|
this.userService.getActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,33 +3,32 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { AdminRoutingModule } from '@pages/admin/admin-routing.module';
|
import { AdminRoutingModule } from '@pages/admin/admin-routing.module';
|
||||||
import { AdminComponent } from '@pages/admin/admin.component';
|
import { AdminComponent } from '@pages/admin/admin.component';
|
||||||
import {SharedModule} from '@app/shared/shared.module';
|
import { SharedModule } from '@app/shared/shared.module';
|
||||||
import {MatCardModule} from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import {MatTableModule} from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import {MatSortModule} from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import {MatPaginatorModule} from '@angular/material/paginator';
|
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {MatRippleModule} from '@angular/material/core';
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AdminComponent],
|
declarations: [AdminComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
AdminRoutingModule,
|
AdminRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatPaginatorModule,
|
MatPaginatorModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatRippleModule
|
MatRippleModule,
|
||||||
]
|
],
|
||||||
})
|
})
|
||||||
export class AdminModule { }
|
export class AdminModule {}
|
||||||
|
@ -5,16 +5,32 @@ import { PagesComponent } from './pages.component';
|
|||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: 'home', component: PagesComponent },
|
{ path: 'home', component: PagesComponent },
|
||||||
{ path: 'tx', loadChildren: () => import('@pages/transactions/transactions.module').then(m => m.TransactionsModule) },
|
{
|
||||||
{ path: 'settings', loadChildren: () => import('@pages/settings/settings.module').then(m => m.SettingsModule) },
|
path: 'tx',
|
||||||
{ path: 'accounts', loadChildren: () => import('@pages/accounts/accounts.module').then(m => m.AccountsModule) },
|
loadChildren: () =>
|
||||||
{ path: 'tokens', loadChildren: () => import('@pages/tokens/tokens.module').then(m => m.TokensModule) },
|
import('@pages/transactions/transactions.module').then((m) => m.TransactionsModule),
|
||||||
{ path: 'admin', loadChildren: () => import('@pages/admin/admin.module').then(m => m.AdminModule) },
|
},
|
||||||
{ path: '**', redirectTo: 'home', pathMatch: 'full'}
|
{
|
||||||
|
path: 'settings',
|
||||||
|
loadChildren: () => import('@pages/settings/settings.module').then((m) => m.SettingsModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'accounts',
|
||||||
|
loadChildren: () => import('@pages/accounts/accounts.module').then((m) => m.AccountsModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tokens',
|
||||||
|
loadChildren: () => import('@pages/tokens/tokens.module').then((m) => m.TokensModule),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'admin',
|
||||||
|
loadChildren: () => import('@pages/admin/admin.module').then((m) => m.AdminModule),
|
||||||
|
},
|
||||||
|
{ path: '**', redirectTo: 'home', pathMatch: 'full' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class PagesRoutingModule { }
|
export class PagesRoutingModule {}
|
||||||
|
@ -15,97 +15,13 @@
|
|||||||
<li class="breadcrumb-item active" aria-current="page">Home</li>
|
<li class="breadcrumb-item active" aria-current="page">Home</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="card">
|
<div class="embed-responsive embed-responsive-16by9">
|
||||||
<mat-card-title class="card-header">
|
<iframe class="embed-responsive-item" [src]="url | safe" allow="fullscreen" loading="lazy"
|
||||||
CICADA DASHBOARD
|
title="Community inclusion currencies dashboard" referrerpolicy="no-referrer">
|
||||||
</mat-card-title>
|
<p>
|
||||||
<div class="col-12">
|
<a href="{{url}}"> Your browser does not support iframes. </a>
|
||||||
<div class="card-body">
|
</p>
|
||||||
<mat-form-field appearance="outline">
|
</iframe>
|
||||||
<mat-label>Filter by location : </mat-label>
|
|
||||||
<mat-select class="ml-2" id="filterUser">
|
|
||||||
<div *ngFor="let county of locations; trackBy: trackByName">
|
|
||||||
<div *ngFor="let district of county.districts; trackBy: trackByName">
|
|
||||||
<mat-optgroup *ngFor="let location of district.locations; trackBy: trackByName" [label]="county.name + ' / ' + district.name + ' / ' + location.name">
|
|
||||||
<mat-option *ngFor="let village of location.villages; trackBy: trackByName" [value]="village">
|
|
||||||
{{village}}
|
|
||||||
</mat-option>
|
|
||||||
</mat-optgroup>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="card col-md-8">
|
|
||||||
<div class="card-body">
|
|
||||||
<div style="display: block">
|
|
||||||
<canvas baseChart
|
|
||||||
[datasets]="lineChartData"
|
|
||||||
[labels]="lineChartLabels"
|
|
||||||
[options]="lineChartOptions"
|
|
||||||
[colors]="lineChartColors"
|
|
||||||
[legend]="lineChartLegend"
|
|
||||||
[chartType]="lineChartType"
|
|
||||||
(chartHover)="chartHovered($event)"
|
|
||||||
(chartClick)="chartClicked($event)">
|
|
||||||
</canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="card-body row">
|
|
||||||
<div class="col-md-3 text-center">
|
|
||||||
<h4>MASTER WALLET BALANCE</h4>
|
|
||||||
<p>{{10000000 | number}} RCU</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 text-center">
|
|
||||||
<h4>TOTAL DISTRIBUTED</h4>
|
|
||||||
<p>{{disbursements * 1000 | number}} RCU</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 text-center">
|
|
||||||
<h4>TOTAL SPENT</h4>
|
|
||||||
<p>{{transactions * 100000 | number}} RCU</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 text-center">
|
|
||||||
<h4>TOTAL USERS</h4>
|
|
||||||
<p>{{users * 10 | number}} users</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="card-body">
|
|
||||||
<div style="display: block">
|
|
||||||
<canvas baseChart
|
|
||||||
[datasets]="barChartData"
|
|
||||||
[labels]="barChartLabels"
|
|
||||||
[options]="barChartOptions"
|
|
||||||
[legend]="barChartLegend"
|
|
||||||
[chartType]="barChartType">
|
|
||||||
</canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card col-md-4">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<h4>TRANSFER USAGES</h4>
|
|
||||||
<div style="display: block">
|
|
||||||
<canvas baseChart
|
|
||||||
[data]="transferUsagesChartData"
|
|
||||||
[labels]="transferUsagesChartLabels"
|
|
||||||
[options]="transferUsagesChartOptions"
|
|
||||||
[colors]="transferUsagesChartColors"
|
|
||||||
[legend]="transferUsagesChartLegend"
|
|
||||||
[chartType]="transferUsagesChartType">
|
|
||||||
</canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<div class="card-body">
|
|
||||||
<h4 class="text-center">PARTNER LIVE FEED</h4>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-footer appMenuSelection></app-footer>
|
<app-footer appMenuSelection></app-footer>
|
||||||
|
@ -8,9 +8,8 @@ describe('PagesComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
declarations: [ PagesComponent ]
|
declarations: [PagesComponent],
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,190 +1,13 @@
|
|||||||
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import {Color, Label} from 'ng2-charts';
|
|
||||||
import {ChartDataSets, ChartOptions, ChartType} from 'chart.js';
|
|
||||||
import {LocationService, LoggingService} from '@app/_services';
|
|
||||||
import {ArraySum} from '@app/_helpers';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pages',
|
selector: 'app-pages',
|
||||||
templateUrl: './pages.component.html',
|
templateUrl: './pages.component.html',
|
||||||
styleUrls: ['./pages.component.scss'],
|
styleUrls: ['./pages.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class PagesComponent implements OnInit {
|
export class PagesComponent {
|
||||||
disbursements: number = 0;
|
url: string = 'https://dashboard.sarafu.network/';
|
||||||
users: any;
|
|
||||||
locations: any;
|
|
||||||
transactions: number = 0;
|
|
||||||
public lineChartData: ChartDataSets[] = [
|
|
||||||
{ data: [65, 59, 80, 81, 56, 55, 40], label: 'User Registration'},
|
|
||||||
{ data: [28, 48, 40, 19, 86, 27, 90], label: 'Transaction Volumes'},
|
|
||||||
{ data: [180, 480, 770, 90, 1000, 270, 400], label: 'Token Disbursements', yAxisID: 'y-axis-1'}
|
|
||||||
];
|
|
||||||
|
|
||||||
public lineChartLabels: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
constructor() {}
|
||||||
|
|
||||||
public lineChartOptions: (ChartOptions & { annotation: any }) = {
|
|
||||||
responsive: true,
|
|
||||||
scales: {
|
|
||||||
// We use this empty structure as a placeholder for dynamic theming.
|
|
||||||
xAxes: [{}],
|
|
||||||
yAxes: [
|
|
||||||
{
|
|
||||||
id: 'y-axis-0',
|
|
||||||
position: 'left',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'y-axis-1',
|
|
||||||
position: 'right',
|
|
||||||
gridLines: {
|
|
||||||
color: 'rgba(255,0,0,0.3)',
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
fontColor: 'red',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
annotation: {
|
|
||||||
annotations: [
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
mode: 'vertical',
|
|
||||||
scaleID: 'x-axis-0',
|
|
||||||
value: 'March',
|
|
||||||
borderColor: 'orange',
|
|
||||||
borderWidth: 2,
|
|
||||||
label: {
|
|
||||||
enabled: true,
|
|
||||||
fontColor: 'orange',
|
|
||||||
content: 'LineAnno'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
public lineChartColors: Color[] = [
|
|
||||||
{ // grey
|
|
||||||
backgroundColor: 'rgba(148,159,177,0.2)',
|
|
||||||
borderColor: 'rgba(148,159,177,1)',
|
|
||||||
pointBackgroundColor: 'rgba(148,159,177,1)',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
|
|
||||||
},
|
|
||||||
{ // dark grey
|
|
||||||
backgroundColor: 'rgba(77,83,96,0.2)',
|
|
||||||
borderColor: 'rgba(77,83,96,1)',
|
|
||||||
pointBackgroundColor: 'rgba(77,83,96,1)',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(77,83,96,1)'
|
|
||||||
},
|
|
||||||
{ // red
|
|
||||||
backgroundColor: 'rgba(255,0,0,0.3)',
|
|
||||||
borderColor: 'red',
|
|
||||||
pointBackgroundColor: 'rgba(148,159,177,1)',
|
|
||||||
pointBorderColor: '#fff',
|
|
||||||
pointHoverBackgroundColor: '#fff',
|
|
||||||
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
public lineChartLegend = true;
|
|
||||||
public lineChartType = 'line';
|
|
||||||
|
|
||||||
public barChartOptions: ChartOptions = {
|
|
||||||
responsive: true,
|
|
||||||
// We use these empty structures as placeholders for dynamic theming.
|
|
||||||
scales: { xAxes: [{}], yAxes: [{}] },
|
|
||||||
plugins: {
|
|
||||||
datalabels: {
|
|
||||||
anchor: 'end',
|
|
||||||
align: 'end',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public barChartLabels: Label[] = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];
|
|
||||||
public barChartType: ChartType = 'horizontalBar';
|
|
||||||
public barChartLegend = true;
|
|
||||||
public barChartData: ChartDataSets[] = [
|
|
||||||
{ data: [65, 59, 80, 81, 56, 55, 40], label: 'New Users'},
|
|
||||||
{ data: [28, 48, 40, 19, 86, 27, 90], label: 'Recurrent Users'}
|
|
||||||
];
|
|
||||||
|
|
||||||
public transferUsagesChartLabels: Label[] = ['Food/Water', 'Fuel/Energy', 'Education', 'Health', 'Shop', 'Environment', 'Transport',
|
|
||||||
'Farming/Labour', 'Savings Group', 'Savings Group'];
|
|
||||||
public transferUsagesChartData: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
||||||
public transferUsagesChartType: ChartType = 'pie';
|
|
||||||
public transferUsagesChartOptions: ChartOptions = {
|
|
||||||
responsive: true,
|
|
||||||
legend: {
|
|
||||||
position: 'top',
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
datalabels: {
|
|
||||||
formatter: (value, ctx) => {
|
|
||||||
const label = ctx.chart.data.labels[ctx.dataIndex];
|
|
||||||
return label;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public transferUsagesChartLegend = true;
|
|
||||||
public transferUsagesChartColors = [
|
|
||||||
{
|
|
||||||
backgroundColor: [
|
|
||||||
'rgba(0,0,255,0.3)',
|
|
||||||
'rgba(255,0,0,0.3)',
|
|
||||||
'rgba(0,255,0,0.3)',
|
|
||||||
'rgba(255,0,255,0.3)',
|
|
||||||
'rgba(255,255,0,0.3)',
|
|
||||||
'rgba(0,255,255,0.3)',
|
|
||||||
'rgba(255,255,255,0.3)',
|
|
||||||
'rgba(255,100,0,0.3)',
|
|
||||||
'rgba(0,255,100,0.3)',
|
|
||||||
'rgba(100,0,255,0.3)'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private locationService: LocationService,
|
|
||||||
private loggingService: LoggingService
|
|
||||||
) {
|
|
||||||
this.locationService.getLocations();
|
|
||||||
this.locationService.locationsSubject.subscribe(locations => {
|
|
||||||
this.locations = locations;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.newDataPoint([50, 80], 'August');
|
|
||||||
this.transferUsagesChartData = [18, 12, 10, 8, 6, 5, 5, 3, 2, 1];
|
|
||||||
this.disbursements = ArraySum.arraySum(this.lineChartData.find(data => data.label === 'Token Disbursements').data);
|
|
||||||
this.users = ArraySum.arraySum(this.barChartData.find(data => data.label === 'New Users').data);
|
|
||||||
this.transactions = ArraySum.arraySum(this.lineChartData.find(data => data.label === 'Transaction Volumes').data);
|
|
||||||
}
|
|
||||||
|
|
||||||
newDataPoint(dataArr: any[], label: string): void {
|
|
||||||
this.barChartData.forEach((dataset, index) => {
|
|
||||||
this.barChartData[index] = Object.assign({}, this.barChartData[index], {
|
|
||||||
data: [...this.barChartData[index].data, dataArr[index]]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.barChartLabels = [...this.barChartLabels, label];
|
|
||||||
}
|
|
||||||
|
|
||||||
public chartClicked({ event, active}: { event: MouseEvent, active: {}[] }): void {
|
|
||||||
this.loggingService.sendInfoLevelMessage(`Event: ${event}, ${active}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public chartHovered({ event, active }: { event: MouseEvent, active: {}[] }): void {
|
|
||||||
this.loggingService.sendInfoLevelMessage(`Event: ${event}, ${active}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public trackByName(index, item): string {
|
|
||||||
return item.name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,27 +3,24 @@ import { CommonModule } from '@angular/common';
|
|||||||
|
|
||||||
import { PagesRoutingModule } from '@pages/pages-routing.module';
|
import { PagesRoutingModule } from '@pages/pages-routing.module';
|
||||||
import { PagesComponent } from '@pages/pages.component';
|
import { PagesComponent } from '@pages/pages.component';
|
||||||
import {SharedModule} from '@app/shared/shared.module';
|
import { SharedModule } from '@app/shared/shared.module';
|
||||||
import {ChartsModule} from 'ng2-charts';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import {MatSelectModule} from '@angular/material/select';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import {MatCardModule} from '@angular/material/card';
|
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [PagesComponent],
|
declarations: [PagesComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
PagesRoutingModule,
|
PagesRoutingModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
ChartsModule,
|
MatButtonModule,
|
||||||
MatButtonModule,
|
MatFormFieldModule,
|
||||||
MatFormFieldModule,
|
MatSelectModule,
|
||||||
MatSelectModule,
|
MatInputModule,
|
||||||
MatInputModule,
|
MatCardModule,
|
||||||
MatCardModule
|
],
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class PagesModule { }
|
export class PagesModule {}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user