Merge branch 'master' into spencer/docs

# Conflicts:
#	package-lock.json
#	package.json
#	src/app/_eth/accountIndex.ts
#	src/app/_eth/token-registry.ts
#	src/app/_guards/auth.guard.ts
#	src/app/_guards/role.guard.ts
#	src/app/_helpers/array-sum.ts
#	src/app/_helpers/clipboard-copy.ts
#	src/app/_helpers/custom-error-state-matcher.ts
#	src/app/_helpers/custom.validator.ts
#	src/app/_helpers/export-csv.ts
#	src/app/_helpers/global-error-handler.ts
#	src/app/_helpers/http-getter.ts
#	src/app/_helpers/mock-backend.ts
#	src/app/_helpers/read-csv.ts
#	src/app/_helpers/schema-validation.ts
#	src/app/_services/user.service.spec.ts
This commit is contained in:
Spencer Ofwiti 2021-05-11 20:41:47 +03:00
commit 5228842e61
173 changed files with 54786 additions and 6103 deletions

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

30
.husky/_/husky.sh Normal file
View 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
View File

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

9
.prettierrc Normal file
View File

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

View File

@ -26,7 +26,8 @@
"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",
@ -68,7 +69,9 @@
"maximumWarning": "6kb", "maximumWarning": "6kb",
"maximumError": "10kb" "maximumError": "10kb"
} }
] ],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
}, },
"dev": { "dev": {
"fileReplacements": [ "fileReplacements": [
@ -110,7 +113,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
View 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)"
]
}
}
]
}

5313
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,10 +8,16 @@
"start:prod": "ng serve --prod", "start:prod": "ng serve --prod",
"build:dev": "ng build -c dev", "build:dev": "ng build -c dev",
"build:prod": "ng build --prod", "build:prod": "ng build --prod",
"start:pwa": "npm run build:prod && http-server -p 4200 dist/cic-staff-client",
"test:dev": "ng test", "test:dev": "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",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"precommit": "format:fix && lint",
"postinstall": "node patch-webpack.js", "postinstall": "node patch-webpack.js",
"prepare": "husky install",
"compodoc": "npx compodoc -p tsconfig.doc.json -d docs/compodoc -n CICADA", "compodoc": "npx compodoc -p tsconfig.doc.json -d docs/compodoc -n CICADA",
"typedoc": "npx typedoc --tsconfig tsconfig.json --exclude **/*.spec.ts --out docs/typedoc --name CICADA src", "typedoc": "npx typedoc --tsconfig tsconfig.json --exclude **/*.spec.ts --out docs/typedoc --name CICADA src",
"docs": "npm run typedoc && npm run compodoc" "docs": "npm run typedoc && npm run compodoc"
@ -28,6 +34,7 @@
"@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",
"@popperjs/core": "^2.5.4", "@popperjs/core": "^2.5.4",
"angular-datatables": "^9.0.2", "angular-datatables": "^9.0.2",
"block-syncer": "^0.2.4", "block-syncer": "^0.2.4",
@ -39,6 +46,7 @@
"datatables.net": "^1.10.22", "datatables.net": "^1.10.22",
"datatables.net-dt": "^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", "mocha": "^8.2.1",
"moolb": "^0.1.0", "moolb": "^0.1.0",
@ -66,6 +74,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",
@ -74,12 +83,22 @@
"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",
"typedoc": "^0.20.36", "typedoc": "^0.20.36",
"typescript": "~4.0.2", "typescript": "~4.0.2",
"yargs": "^13.3.2" "yargs": "^13.3.2"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged & ng lint"
}
} }
} }

View File

@ -76,7 +76,7 @@ export class AccountIndex {
* @returns true - If the address has been registered in the accounts registry. * @returns true - If the address has been registered in the accounts registry.
*/ */
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.accountIndex(address).call()) !== 0;
} }
/** /**

View File

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

View File

@ -2,7 +2,7 @@
import Web3 from 'web3'; import Web3 from 'web3';
// Application imports // Application imports
import {environment} from '@src/environments/environment'; import { environment } from '@src/environments/environment';
/** Fetch the token registry contract's ABI. */ /** Fetch the token registry contract's ABI. */

View File

@ -12,7 +12,7 @@ import { Observable } from 'rxjs';
* @implements CanActivate * @implements CanActivate
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
@ -34,12 +34,12 @@ export class AuthGuard implements CanActivate {
*/ */
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 {
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) { if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
return true; return true;
} }
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
return false; return false;
} }
} }

View File

@ -12,7 +12,7 @@ import { Observable } from 'rxjs';
* @implements CanActivate * @implements CanActivate
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class RoleGuard implements CanActivate { export class RoleGuard implements CanActivate {
@ -34,7 +34,8 @@ export class RoleGuard implements CanActivate {
*/ */
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) {
@ -44,8 +45,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;
} }
} }

View File

@ -14,7 +14,6 @@ function arraySum(arr: Array<number>): number {
return arr.reduce((accumulator, current) => accumulator + current, 0); return arr.reduce((accumulator, current) => accumulator + current, 0);
} }
/** @exports */ /** @exports */
export { export { arraySum };
arraySum
};

View File

@ -21,10 +21,10 @@ function copyToClipboard(text: any): boolean {
// check and see if the user had a text selection range // check and see if the user had a text selection range
let currentRange: Range | boolean; let currentRange: Range | boolean;
if (document.getSelection().rangeCount > 0) { if (document.getSelection().rangeCount > 0) {
// the user has a text selection range, store it // the user has a text selection range, store it
currentRange = document.getSelection().getRangeAt(0); currentRange = document.getSelection().getRangeAt(0);
// remove the current selection // remove the current selection
window.getSelection().removeRange(currentRange); window.getSelection().removeRange(currentRange);
} else { } else {
// they didn't have anything selected // they didn't have anything selected
currentRange = false; currentRange = false;
@ -44,7 +44,7 @@ function copyToClipboard(text: any): boolean {
// copy the text // copy the text
document.execCommand('copy'); document.execCommand('copy');
} catch (err) { } catch (err) {
window.alert('Your Browser Doesn\'t support this! Error : ' + err); window.alert('Your Browser Does not support this! Error : ' + err);
return false; return false;
} }
// remove the selection range (Chrome throws a warning if we don't.) // remove the selection range (Chrome throws a warning if we don't.)
@ -61,6 +61,4 @@ function copyToClipboard(text: any): boolean {
} }
/** @exports */ /** @exports */
export { export { copyToClipboard };
copyToClipboard
};

View File

@ -1,13 +1,13 @@
// Core imports // Core imports
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms'; import { ErrorStateMatcher } from '@angular/material/core';
import {ErrorStateMatcher} from '@angular/material/core'; import { FormControl, FormGroupDirective, NgForm } from '@angular/forms';
/** /**
* Custom provider that defines how form controls behave with regards to displaying error messages. * Custom provider that defines how form controls behave with regards to displaying error messages.
* *
* @implements ErrorStateMatcher * @implements ErrorStateMatcher
*/ */
export class CustomErrorStateMatcher implements ErrorStateMatcher{ export class CustomErrorStateMatcher implements ErrorStateMatcher {
/** /**
* Checks whether an invalid input has been made and an error should be made. * Checks whether an invalid input has been made and an error should be made.
* *

View File

@ -1,5 +1,5 @@
// Core imports // Core imports
import {AbstractControl, ValidationErrors} from '@angular/forms'; import { AbstractControl, ValidationErrors } from '@angular/forms';
/** /**
* Provides methods to perform custom validation to form inputs. * Provides methods to perform custom validation to form inputs.

View File

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

View File

@ -71,9 +71,9 @@ export class GlobalErrorHandler extends ErrorHandler {
const isWarning: boolean = this.isWarning(errorTraceString); const isWarning: boolean = this.isWarning(errorTraceString);
if (isWarning) { if (isWarning) {
this.loggingService.sendWarnLevelMessage(errorTraceString, {error}); this.loggingService.sendWarnLevelMessage(errorTraceString, { error });
} else { } else {
this.loggingService.sendErrorLevelMessage(errorTraceString, this, {error}); this.loggingService.sendErrorLevelMessage(errorTraceString, this, { error });
} }
throw error; throw error;
@ -110,14 +110,30 @@ export class GlobalErrorHandler extends ErrorHandler {
const route: string = this.router.url; const route: string = this.router.url;
if (error instanceof HttpErrorResponse) { if (error instanceof HttpErrorResponse) {
this.loggingService.sendErrorLevelMessage( this.loggingService.sendErrorLevelMessage(
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(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 }
);
} }
} }
} }

View File

@ -7,20 +7,19 @@ function HttpGetter(): void {}
* @param filename - The filename to fetch. * @param filename - The filename to fetch.
* @returns The HTTP response text. * @returns The HTTP response text.
*/ */
HttpGetter.prototype.get = (filename: string) => new Promise((resolve, reject) => { HttpGetter.prototype.get = (filename) =>
const xhr: XMLHttpRequest = new XMLHttpRequest(); new Promise((resolve, reject) => {
xhr.addEventListener('load', (e) => { const xhr: XMLHttpRequest = new XMLHttpRequest();
if (xhr.status === 200) { xhr.addEventListener('load', (e) => {
resolve(xhr.responseText); if (xhr.status === 200) {
return; resolve(xhr.responseText);
} return;
reject('failed with status ' + xhr.status + ': ' + xhr.statusText); }
reject('failed with status ' + xhr.status + ': ' + xhr.statusText);
});
xhr.open('GET', filename);
xhr.send();
}); });
xhr.open('GET', filename);
xhr.send();
});
/** @exports */ /** @exports */
export { export { HttpGetter };
HttpGetter
};

View File

@ -19,7 +19,7 @@ const actions: Array<Action> = [
{ id: 3, user: 'Will', role: 'superadmin', action: 'Reclaim RSV 1000', approval: true }, { id: 3, user: 'Will', role: 'superadmin', action: 'Reclaim RSV 1000', approval: true },
{ id: 4, user: 'Vivian', role: 'enroller', action: 'Complete user profile', approval: true }, { id: 4, user: 'Vivian', role: 'enroller', action: 'Complete user profile', approval: true },
{ id: 5, user: 'Jack', role: 'enroller', action: 'Reclaim RSV 200', approval: false }, { id: 5, user: 'Jack', role: 'enroller', action: 'Reclaim RSV 200', approval: false },
{ id: 6, user: 'Patience', role: 'enroller', action: 'Change user information', approval: false } { id: 6, user: 'Patience', role: 'enroller', action: 'Change user information', approval: false },
]; ];
/** A mock of curated area names. */ /** A mock of curated area names. */
@ -112,103 +112,634 @@ const areaTypes: Array<AreaType> = [
const categories: Array<Category> = [ const categories: Array<Category> = [
{ {
name: 'system', name: 'system',
products: ['system', 'office main', 'office main phone'] products: ['system', 'office main', 'office main phone'],
}, },
{ {
name: 'education', name: 'education',
products: ['book', 'coach', 'teacher', 'sch', 'school', 'pry', 'education', 'student', 'mwalimu', 'maalim', 'consultant', 'consult', products: [
'college', 'university', 'lecturer', 'primary', 'secondary', 'daycare', 'babycare', 'baby care', 'elim', 'eimu', 'nursery', 'book',
'red cross', 'volunteer', 'instructor', 'journalist', 'lesson', 'academy', 'headmistress', 'headteacher', 'cyber', 'researcher', 'coach',
'professor', 'demo', 'expert', 'tution', 'tuition', 'children', 'headmaster', 'educator', 'Marital counsellor', 'counsellor', 'teacher',
'trainer', 'vijana', 'youth', 'intern', 'redcross', 'KRCS', 'danish', 'science', 'data', 'facilitator', 'vitabu', 'kitabu'] 'sch',
'school',
'pry',
'education',
'student',
'mwalimu',
'maalim',
'consultant',
'consult',
'college',
'university',
'lecturer',
'primary',
'secondary',
'daycare',
'babycare',
'baby care',
'elim',
'eimu',
'nursery',
'red cross',
'volunteer',
'instructor',
'journalist',
'lesson',
'academy',
'headmistress',
'headteacher',
'cyber',
'researcher',
'professor',
'demo',
'expert',
'tution',
'tuition',
'children',
'headmaster',
'educator',
'Marital counsellor',
'counsellor',
'trainer',
'vijana',
'youth',
'intern',
'redcross',
'KRCS',
'danish',
'science',
'data',
'facilitator',
'vitabu',
'kitabu',
],
}, },
{ {
name: 'faith', name: 'faith',
products: ['pastor', 'imam', 'madrasa', 'religous', 'religious', 'ustadh', 'ustadhi', 'Marital counsellor', 'counsellor', 'church', products: [
'kanisa', 'mksiti', 'donor'] 'pastor',
'imam',
'madrasa',
'religous',
'religious',
'ustadh',
'ustadhi',
'Marital counsellor',
'counsellor',
'church',
'kanisa',
'mksiti',
'donor',
],
}, },
{ {
name: 'government', name: 'government',
products: ['elder', 'chief', 'police', 'government', 'country', 'county', 'soldier', 'village admin', 'ward', 'leader', 'kra', products: [
'mailman', 'immagration', 'immigration'] 'elder',
'chief',
'police',
'government',
'country',
'county',
'soldier',
'village admin',
'ward',
'leader',
'kra',
'mailman',
'immagration',
'immigration',
],
}, },
{ {
name: 'environment', name: 'environment',
products: ['conservation', 'toilet', 'choo', 'garbage', 'fagio', 'waste', 'tree', 'taka', 'scrap', 'cleaning', 'gardener', 'rubbish', products: [
'usafi', 'mazingira', 'miti', 'trash', 'cleaner', 'plastic', 'collection', 'seedling', 'seedlings', 'recycling'] 'conservation',
'toilet',
'choo',
'garbage',
'fagio',
'waste',
'tree',
'taka',
'scrap',
'cleaning',
'gardener',
'rubbish',
'usafi',
'mazingira',
'miti',
'trash',
'cleaner',
'plastic',
'collection',
'seedling',
'seedlings',
'recycling',
],
}, },
{ {
name: 'farming', name: 'farming',
products: ['farm', 'farmer', 'farming', 'mkulima', 'kulima', 'ukulima', 'wakulima', 'jembe', 'shamba'] products: [
'farm',
'farmer',
'farming',
'mkulima',
'kulima',
'ukulima',
'wakulima',
'jembe',
'shamba',
],
}, },
{ {
name: 'labour', name: 'labour',
products: ['artist', 'agent', 'guard', 'askari', 'accountant', 'baker', 'beadwork', 'beauty', 'business', 'barber', 'casual', products: [
'electrian', 'caretaker', 'car wash', 'capenter', 'construction', 'chef', 'catering', 'cobler', 'cobbler', 'carwash', 'dhobi', 'artist',
'landlord', 'design', 'carpenter', 'fundi', 'hawking', 'hawker', 'househelp', 'hsehelp', 'house help', 'help', 'housegirl', 'kushona', 'agent',
'juakali', 'jualikali', 'juacali', 'jua kali', 'shepherd', 'makuti', 'kujenga', 'kinyozi', 'kazi', 'knitting', 'kufua', 'fua', 'guard',
'hustler', 'biashara', 'labour', 'labor', 'laundry', 'repair', 'hair', 'posho', 'mill', 'mtambo', 'uvuvi', 'engineer', 'manager', 'askari',
'tailor', 'nguo', 'mason', 'mtumba', 'garage', 'mechanic', 'mjenzi', 'mfugaji', 'painter', 'receptionist', 'printing', 'programming', 'accountant',
'plumb', 'charging', 'salon', 'mpishi', 'msusi', 'mgema', 'footballer', 'photocopy', 'peddler', 'staff', 'sales', 'service', 'saloon', 'baker',
'seremala', 'security', 'insurance', 'secretary', 'shoe', 'shepard', 'shephard', 'tout', 'tv', 'mvuvi', 'mawe', 'majani', 'maembe', 'beadwork',
'freelance', 'mjengo', 'electronics', 'photographer', 'programmer', 'electrician', 'washing', 'bricks', 'welder', 'welding', 'beauty',
'working', 'worker', 'watchman', 'waiter', 'waitress', 'viatu', 'yoga', 'guitarist', 'house', 'artisan', 'musician', 'trade', 'business',
'makonge', 'ujenzi', 'vendor', 'watchlady', 'marketing', 'beautician', 'photo', 'metal work', 'supplier', 'law firm', 'brewer'] 'barber',
'casual',
'electrian',
'caretaker',
'car wash',
'capenter',
'construction',
'chef',
'catering',
'cobler',
'cobbler',
'carwash',
'dhobi',
'landlord',
'design',
'carpenter',
'fundi',
'hawking',
'hawker',
'househelp',
'hsehelp',
'house help',
'help',
'housegirl',
'kushona',
'juakali',
'jualikali',
'juacali',
'jua kali',
'shepherd',
'makuti',
'kujenga',
'kinyozi',
'kazi',
'knitting',
'kufua',
'fua',
'hustler',
'biashara',
'labour',
'labor',
'laundry',
'repair',
'hair',
'posho',
'mill',
'mtambo',
'uvuvi',
'engineer',
'manager',
'tailor',
'nguo',
'mason',
'mtumba',
'garage',
'mechanic',
'mjenzi',
'mfugaji',
'painter',
'receptionist',
'printing',
'programming',
'plumb',
'charging',
'salon',
'mpishi',
'msusi',
'mgema',
'footballer',
'photocopy',
'peddler',
'staff',
'sales',
'service',
'saloon',
'seremala',
'security',
'insurance',
'secretary',
'shoe',
'shepard',
'shephard',
'tout',
'tv',
'mvuvi',
'mawe',
'majani',
'maembe',
'freelance',
'mjengo',
'electronics',
'photographer',
'programmer',
'electrician',
'washing',
'bricks',
'welder',
'welding',
'working',
'worker',
'watchman',
'waiter',
'waitress',
'viatu',
'yoga',
'guitarist',
'house',
'artisan',
'musician',
'trade',
'makonge',
'ujenzi',
'vendor',
'watchlady',
'marketing',
'beautician',
'photo',
'metal work',
'supplier',
'law firm',
'brewer',
],
}, },
{ {
name: 'food', name: 'food',
products: ['avocado', 'bhajia', 'bajia', 'mbonga', 'bofu', 'beans', 'biscuits', 'biringanya', 'banana', 'bananas', 'crisps', 'chakula', products: [
'coconut', 'chapati', 'cereal', 'chipo', 'chapo', 'chai', 'chips', 'cassava', 'cake', 'cereals', 'cook', 'corn', 'coffee', 'chicken', 'avocado',
'dagaa', 'donut', 'dough', 'groundnuts', 'hotel', 'holel', 'hoteli', 'butcher', 'butchery', 'fruit', 'food', 'fruits', 'fish', 'bhajia',
'githeri', 'grocery', 'grocer', 'pojo', 'papa', 'goats', 'mabenda', 'mbenda', 'poultry', 'soda', 'peanuts', 'potatoes', 'samosa', 'bajia',
'soko', 'samaki', 'tomato', 'tomatoes', 'mchele', 'matunda', 'mango', 'melon', 'mellon', 'nyanya', 'nyama', 'omena', 'umena', 'ndizi', 'mbonga',
'njugu', 'kamba kamba', 'khaimati', 'kaimati', 'kunde', 'kuku', 'kahawa', 'keki', 'muguka', 'miraa', 'milk', 'choma', 'maziwa', 'bofu',
'mboga', 'mbog', 'busaa', 'chumvi', 'cabbages', 'mabuyu', 'machungwa', 'mbuzi', 'mnazi', 'mchicha', 'ngombe', 'ngano', 'nazi', 'beans',
'oranges', 'peanuts', 'mkate', 'bread', 'mikate', 'vitungu', 'sausages', 'maize', 'mbata', 'mchuzi', 'mchuuzi', 'mandazi', 'mbaazi', 'biscuits',
'mahindi', 'maandazi', 'mogoka', 'meat', 'mhogo', 'mihogo', 'muhogo', 'maharagwe', 'miwa', 'mahamri', 'mitumba', 'simsim', 'porridge', 'biringanya',
'pilau', 'vegetable', 'egg', 'mayai', 'mifugo', 'unga', 'good', 'sima', 'sweet', 'sweats', 'sambusa', 'snacks', 'sugar', 'suger', 'banana',
'ugoro', 'sukari', 'soup', 'spinach', 'smokie', 'smokies', 'sukuma', 'tea', 'uji', 'ugali', 'uchuzi', 'uchuuzi', 'viazi', 'yoghurt', 'bananas',
'yogurt', 'wine', 'marondo', 'maandzi', 'matoke', 'omeno', 'onions', 'nzugu', 'korosho', 'barafu', 'juice'] 'crisps',
'chakula',
'coconut',
'chapati',
'cereal',
'chipo',
'chapo',
'chai',
'chips',
'cassava',
'cake',
'cereals',
'cook',
'corn',
'coffee',
'chicken',
'dagaa',
'donut',
'dough',
'groundnuts',
'hotel',
'holel',
'hoteli',
'butcher',
'butchery',
'fruit',
'food',
'fruits',
'fish',
'githeri',
'grocery',
'grocer',
'pojo',
'papa',
'goats',
'mabenda',
'mbenda',
'poultry',
'soda',
'peanuts',
'potatoes',
'samosa',
'soko',
'samaki',
'tomato',
'tomatoes',
'mchele',
'matunda',
'mango',
'melon',
'mellon',
'nyanya',
'nyama',
'omena',
'umena',
'ndizi',
'njugu',
'kamba kamba',
'khaimati',
'kaimati',
'kunde',
'kuku',
'kahawa',
'keki',
'muguka',
'miraa',
'milk',
'choma',
'maziwa',
'mboga',
'mbog',
'busaa',
'chumvi',
'cabbages',
'mabuyu',
'machungwa',
'mbuzi',
'mnazi',
'mchicha',
'ngombe',
'ngano',
'nazi',
'oranges',
'peanuts',
'mkate',
'bread',
'mikate',
'vitungu',
'sausages',
'maize',
'mbata',
'mchuzi',
'mchuuzi',
'mandazi',
'mbaazi',
'mahindi',
'maandazi',
'mogoka',
'meat',
'mhogo',
'mihogo',
'muhogo',
'maharagwe',
'miwa',
'mahamri',
'mitumba',
'simsim',
'porridge',
'pilau',
'vegetable',
'egg',
'mayai',
'mifugo',
'unga',
'good',
'sima',
'sweet',
'sweats',
'sambusa',
'snacks',
'sugar',
'suger',
'ugoro',
'sukari',
'soup',
'spinach',
'smokie',
'smokies',
'sukuma',
'tea',
'uji',
'ugali',
'uchuzi',
'uchuuzi',
'viazi',
'yoghurt',
'yogurt',
'wine',
'marondo',
'maandzi',
'matoke',
'omeno',
'onions',
'nzugu',
'korosho',
'barafu',
'juice',
],
}, },
{ {
name: 'water', name: 'water',
products: ['maji', 'water'] products: ['maji', 'water'],
}, },
{ {
name: 'health', name: 'health',
products: ['agrovet', 'dispensary', 'barakoa', 'chemist', 'Chemicals', 'chv', 'doctor', 'daktari', 'dawa', 'hospital', 'herbalist', products: [
'mganga', 'sabuni', 'soap', 'nurse', 'heath', 'community health worker', 'clinic', 'clinical', 'mask', 'medicine', 'lab technician', 'agrovet',
'pharmacy', 'cosmetics', 'veterinary', 'vet', 'sickly', 'emergency response', 'emergency'] 'dispensary',
'barakoa',
'chemist',
'Chemicals',
'chv',
'doctor',
'daktari',
'dawa',
'hospital',
'herbalist',
'mganga',
'sabuni',
'soap',
'nurse',
'heath',
'community health worker',
'clinic',
'clinical',
'mask',
'medicine',
'lab technician',
'pharmacy',
'cosmetics',
'veterinary',
'vet',
'sickly',
'emergency response',
'emergency',
],
}, },
{ {
name: 'savings', name: 'savings',
products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'] products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'],
}, },
{ {
name: 'shop', name: 'shop',
products: ['bag', 'bead', 'belt', 'bedding', 'jik', 'bed', 'cement', 'botique', 'boutique', 'lines', 'kibanda', 'kiosk', 'spareparts', products: [
'candy', 'cloth', 'electricals', 'mutumba', 'cafe', 'leso', 'lesso', 'duka', 'spare parts', 'socks', 'malimali', 'mitungi', 'bag',
'mali mali', 'hardware', 'detergent', 'detergents', 'dera', 'retail', 'kamba', 'pombe', 'pampers', 'pool', 'phone', 'simu', 'mangwe', 'bead',
'mikeka', 'movie', 'shop', 'acces', 'mchanga', 'uto', 'airtime', 'matress', 'mattress', 'mattresses', 'mpsea', 'mpesa', 'shirt', 'belt',
'wholesaler', 'perfume', 'playstation', 'tissue', 'vikapu', 'uniform', 'flowers', 'vitenge', 'utencils', 'utensils', 'station', 'bedding',
'jewel', 'pool table', 'club', 'pub', 'bar', 'furniture', 'm-pesa', 'vyombo'] 'jik',
'bed',
'cement',
'botique',
'boutique',
'lines',
'kibanda',
'kiosk',
'spareparts',
'candy',
'cloth',
'electricals',
'mutumba',
'cafe',
'leso',
'lesso',
'duka',
'spare parts',
'socks',
'malimali',
'mitungi',
'mali mali',
'hardware',
'detergent',
'detergents',
'dera',
'retail',
'kamba',
'pombe',
'pampers',
'pool',
'phone',
'simu',
'mangwe',
'mikeka',
'movie',
'shop',
'acces',
'mchanga',
'uto',
'airtime',
'matress',
'mattress',
'mattresses',
'mpsea',
'mpesa',
'shirt',
'wholesaler',
'perfume',
'playstation',
'tissue',
'vikapu',
'uniform',
'flowers',
'vitenge',
'utencils',
'utensils',
'station',
'jewel',
'pool table',
'club',
'pub',
'bar',
'furniture',
'm-pesa',
'vyombo',
],
}, },
{ {
name: 'transport', name: 'transport',
products: ['kebeba', 'beba', 'bebabeba', 'bike', 'bicycle', 'matatu', 'boda', 'bodaboda', 'cart', 'carrier', 'tour', 'travel', 'driver', products: [
'dereva', 'tout', 'conductor', 'kubeba', 'tuktuk', 'taxi', 'piki', 'pikipiki', 'manamba', 'trasportion', 'mkokoteni', 'mover', 'kebeba',
'motorist', 'motorbike', 'transport', 'transpoter', 'gari', 'magari', 'makanga', 'car'] 'beba',
'bebabeba',
'bike',
'bicycle',
'matatu',
'boda',
'bodaboda',
'cart',
'carrier',
'tour',
'travel',
'driver',
'dereva',
'tout',
'conductor',
'kubeba',
'tuktuk',
'taxi',
'piki',
'pikipiki',
'manamba',
'trasportion',
'mkokoteni',
'mover',
'motorist',
'motorbike',
'transport',
'transpoter',
'gari',
'magari',
'makanga',
'car',
],
}, },
{ {
name: 'fuel/energy', name: 'fuel/energy',
products: ['timber', 'timberyard', 'biogas', 'charcol', 'charcoal', 'kuni', 'mbao', 'fuel', 'makaa', 'mafuta', 'moto', 'solar', 'stima', products: [
'fire', 'firewood', 'wood', 'oil', 'taa', 'gas', 'paraffin', 'parrafin', 'parafin', 'petrol', 'petro', 'kerosine', 'kerosene', 'timber',
'diesel'] 'timberyard',
'biogas',
'charcol',
'charcoal',
'kuni',
'mbao',
'fuel',
'makaa',
'mafuta',
'moto',
'solar',
'stima',
'fire',
'firewood',
'wood',
'oil',
'taa',
'gas',
'paraffin',
'parrafin',
'parafin',
'petrol',
'petro',
'kerosine',
'kerosene',
'diesel',
],
}, },
{ {
name: 'other', name: 'other',
products: ['other', 'none', 'unknown', 'none'] products: ['other', 'none', 'unknown', 'none'],
} },
]; ];
/** A mock of curated genders */ /** A mock of curated genders */
@ -336,22 +867,26 @@ export class MockBackendInterceptor implements HttpInterceptor {
} }
function getAreaNames(): Observable<HttpResponse<any>> { function getAreaNames(): Observable<HttpResponse<any>> {
const areaNameList: Array<string> = areaNames.map(areaName => areaName.name); const areaNameList: Array<string> = areaNames.map((areaName) => areaName.name);
return ok(areaNameList); return ok(areaNameList);
} }
function getAreaNameByLocation(): Observable<HttpResponse<any>> { function getAreaNameByLocation(): Observable<HttpResponse<any>> {
const queriedAreaName: AreaName = areaNames.find(areaName => areaName.locations.includes(stringFromUrl())); const queriedAreaName: AreaName = areaNames.find((areaName) =>
areaName.locations.includes(stringFromUrl())
);
return ok(queriedAreaName.name); return ok(queriedAreaName.name);
} }
function getAreaTypes(): Observable<HttpResponse<any>> { function getAreaTypes(): Observable<HttpResponse<any>> {
const areaTypeList: Array<string> = areaTypes.map(areaType => areaType.name); const areaTypeList: Array<string> = areaTypes.map((areaType) => areaType.name);
return ok(areaTypeList); return ok(areaTypeList);
} }
function getAreaTypeByArea(): Observable<HttpResponse<any>> { function getAreaTypeByArea(): Observable<HttpResponse<any>> {
const queriedAreaType: AreaType = areaTypes.find(areaType => areaType.area.includes(stringFromUrl())); const queriedAreaType: AreaType = areaTypes.find((areaType) =>
areaType.area.includes(stringFromUrl())
);
return ok(queriedAreaType.name); return ok(queriedAreaType.name);
} }
@ -412,5 +947,5 @@ export class MockBackendInterceptor implements HttpInterceptor {
export const MockBackendProvider = { export const MockBackendProvider = {
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: MockBackendInterceptor, useClass: MockBackendInterceptor,
multi: true multi: true,
}; };

View File

@ -1,7 +1,7 @@
/** An object defining the properties of the data read. */ /** An object defining the properties of the data read. */
const objCsv: { size: number, dataFile: any } = { const objCsv: { size: number; dataFile: any } = {
size: 0, size: 0,
dataFile: [] dataFile: [],
}; };
/** /**
@ -14,7 +14,7 @@ function readCsv(input: any): Array<any> | void {
if (input.files && input.files[0]) { if (input.files && input.files[0]) {
const reader: FileReader = new FileReader(); const reader: FileReader = new FileReader();
reader.readAsBinaryString(input.files[0]); reader.readAsBinaryString(input.files[0]);
reader.onload = event => { reader.onload = (event) => {
objCsv.size = event.total; objCsv.size = event.total;
objCsv.dataFile = event.target.result; objCsv.dataFile = event.target.result;
return parseData(objCsv.dataFile); return parseData(objCsv.dataFile);
@ -31,7 +31,7 @@ function readCsv(input: any): Array<any> | void {
function parseData(data: any): Array<any> { function parseData(data: any): Array<any> {
const csvData: Array<any> = []; const csvData: Array<any> = [];
const lineBreak: Array<any> = data.split('\n'); const lineBreak: Array<any> = data.split('\n');
lineBreak.forEach(res => { lineBreak.forEach((res) => {
csvData.push(res.split(',')); csvData.push(res.split(','));
}); });
console.table(csvData); console.table(csvData);
@ -39,6 +39,4 @@ function parseData(data: any): Array<any> {
} }
/** @exports */ /** @exports */
export { export { readCsv };
readCsv
};

View File

@ -10,7 +10,7 @@ async function personValidation(person: any): Promise<void> {
const personValidationErrors: any = await validatePerson(person); const personValidationErrors: any = await validatePerson(person);
if (personValidationErrors) { if (personValidationErrors) {
personValidationErrors.map(error => console.error(`${error.message}`)); personValidationErrors.map((error) => console.error(`${error.message}`));
} }
} }
@ -23,12 +23,9 @@ async function vcardValidation(vcard: any): Promise<void> {
const vcardValidationErrors: any = await validateVcard(vcard); const vcardValidationErrors: any = await validateVcard(vcard);
if (vcardValidationErrors) { if (vcardValidationErrors) {
vcardValidationErrors.map(error => console.error(`${error.message}`)); vcardValidationErrors.map((error) => console.error(`${error.message}`));
} }
} }
/** @exports */ /** @exports */
export { export { personValidation, vcardValidation };
personValidation,
vcardValidation,
};

View File

@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { ErrorInterceptor } from '@app/_interceptors/error.interceptor'; import { ErrorInterceptor } from '@app/_interceptors/error.interceptor';
describe('ErrorInterceptor', () => { describe('ErrorInterceptor', () => {
beforeEach(() => 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);

View File

@ -1,18 +1,18 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
HttpRequest, HttpRequest,
HttpHandler, HttpHandler,
HttpEvent, HttpEvent,
HttpInterceptor, HttpErrorResponse HttpInterceptor,
HttpErrorResponse,
} from '@angular/common/http'; } from '@angular/common/http';
import {Observable, throwError} from 'rxjs'; import { Observable, throwError } from 'rxjs';
import {catchError} from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import {ErrorDialogService, LoggingService} from '@app/_services'; import { ErrorDialogService, LoggingService } from '@app/_services';
import {Router} from '@angular/router'; import { Router } from '@angular/router';
@Injectable() @Injectable()
export class ErrorInterceptor implements HttpInterceptor { export class ErrorInterceptor implements HttpInterceptor {
constructor( constructor(
private errorDialogService: ErrorDialogService, private errorDialogService: ErrorDialogService,
private loggingService: LoggingService, private loggingService: LoggingService,
@ -29,11 +29,13 @@ export class ErrorInterceptor implements HttpInterceptor {
} else { } else {
// The backend returned an unsuccessful response code. // The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong. // The response body may contain clues as to what went wrong.
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(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

View File

@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { HttpConfigInterceptor } from './http-config.interceptor'; import { HttpConfigInterceptor } from './http-config.interceptor';
describe('HttpConfigInterceptor', () => { describe('HttpConfigInterceptor', () => {
beforeEach(() => 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);

View File

@ -1,15 +1,9 @@
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>> {

View File

@ -3,11 +3,11 @@ import { TestBed } from '@angular/core/testing';
import { LoggingInterceptor } from './logging.interceptor'; import { LoggingInterceptor } from './logging.interceptor';
describe('LoggingInterceptor', () => { describe('LoggingInterceptor', () => {
beforeEach(() => 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);

View File

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

View File

@ -20,24 +20,34 @@ 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;
}
];
}; };
} }
@ -75,31 +85,35 @@ 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 { export { AccountDetails, Signature, Meta, MetaResponse, defaultAccount };
AccountDetails,
Signature,
Meta,
MetaResponse,
defaultAccount
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,8 +31,7 @@ interface MutableKeyStore extends KeyStore {
sign(plainText: string): Promise<any>; sign(plainText: string): Promise<any>;
} }
class MutablePgpKeyStore implements MutableKeyStore{ class MutablePgpKeyStore implements MutableKeyStore {
async loadKeyring(): Promise<void> { async loadKeyring(): Promise<void> {
await keyring.load(); await keyring.load();
await keyring.store(); await keyring.store();
@ -77,8 +76,8 @@ class MutablePgpKeyStore implements MutableKeyStore{
async isValidKey(key): Promise<boolean> { async isValidKey(key): Promise<boolean> {
// There is supposed to be an openpgp.readKey() method but I can't find it? // There is supposed to be an openpgp.readKey() method but I can't find it?
const _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> {
@ -93,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 {
@ -103,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> {
@ -135,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 +166,4 @@ class MutablePgpKeyStore implements MutableKeyStore{
} }
} }
export { export { MutablePgpKeyStore, MutableKeyStore };
MutablePgpKeyStore,
MutableKeyStore
};

View File

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

View File

@ -1,5 +1,5 @@
import {MutableKeyStore} from '@app/_pgp/pgp-key-store'; import { MutableKeyStore } from '@app/_pgp/pgp-key-store';
import {LoggingService} from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
const openpgp = require('openpgp'); const openpgp = require('openpgp');
@ -7,12 +7,12 @@ interface Signable {
digest(): string; digest(): string;
} }
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
};

View File

@ -1,15 +1,15 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {hobaParseChallengeHeader} from '@src/assets/js/hoba.js'; import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js';
import {signChallenge} from '@src/assets/js/hoba-pgp.js'; import { signChallenge } from '@src/assets/js/hoba-pgp.js';
import {environment} from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import {LoggingService} from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp'; import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp';
import {ErrorDialogService} from '@app/_services/error-dialog.service'; import { ErrorDialogService } from '@app/_services/error-dialog.service';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {HttpError} from '@app/_helpers/global-error-handler'; import { HttpError } from '@app/_helpers/global-error-handler';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class AuthService { export class AuthService {
sessionToken: any; sessionToken: any;
@ -68,8 +68,8 @@ export class AuthService {
xhr.setRequestHeader('x-cic-automerge', 'none'); xhr.setRequestHeader('x-cic-automerge', 'none');
xhr.addEventListener('load', (e) => { xhr.addEventListener('load', (e) => {
if (xhr.status !== 200) { if (xhr.status !== 200) {
const error = new HttpError(xhr.statusText, xhr.status); const error = new HttpError(xhr.statusText, xhr.status);
return reject(error); return reject(error);
} }
this.sessionToken = xhr.getResponseHeader('Token'); this.sessionToken = xhr.getResponseHeader('Token');
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken); sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
@ -95,50 +95,53 @@ export class AuthService {
xhr.send(); xhr.send();
} }
login(): boolean { login(): boolean {
if (this.sessionToken !== undefined) { if (this.sessionToken !== undefined) {
try { try {
this.getWithToken(); this.getWithToken();
return true; return true;
} catch (e) { } catch (e) {
this.loggingService.sendErrorLevelMessage('Login token failed', this, {error: e}); this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
} }
} else { } else {
try { try {
this.getChallenge(); this.getChallenge();
} catch (e) { } catch (e) {
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, {error: e}); this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e });
} }
} }
return false; return false;
} }
async loginResponse(o: { challenge: string; realm: any }): Promise<any> {
async loginResponse(o: { challenge: string, realm: any }): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await signChallenge(o.challenge, const r = await signChallenge(
o.realm, o.challenge,
environment.cicMetaUrl, o.realm,
this.mutableKeyStore); environment.cicMetaUrl,
this.mutableKeyStore
);
const sessionTokenResult: boolean = await this.sendResponse(r); const sessionTokenResult: boolean = await this.sendResponse(r);
} catch (error) { } catch (error) {
if (error instanceof HttpError) { if (error instanceof HttpError) {
if (error.status === 403) { if (error.status === 403) {
this.errorDialogService.openDialog({ message: 'You are not authorized to use this system' }); this.errorDialogService.openDialog({
message: 'You are not authorized to use this system',
});
} }
if (error.status === 401) { if (error.status === 401) {
this.errorDialogService.openDialog({ this.errorDialogService.openDialog({
message: 'Unable to authenticate with the service. ' + message:
'Unable to authenticate with the service. ' +
'Please speak with the staff at Grassroots ' + 'Please speak with the staff at Grassroots ' +
'Economics for requesting access ' + 'Economics for requesting access ' +
'staff@grassrootseconomics.net.' 'staff@grassrootseconomics.net.',
}); });
} }
} }
// TODO define this error // TODO define this error
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'}); this.errorDialogService.openDialog({ message: 'Incorrect key passphrase.' });
resolve(false); resolve(false);
} }
}); });
@ -164,7 +167,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}`,
}); });
@ -177,27 +184,26 @@ export class AuthService {
logout(): void { logout(): void {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN')); sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
this.sessionToken = undefined; this.sessionToken = undefined;
window.location.reload(true); window.location.reload();
} }
getTrustedUsers(): any { getTrustedUsers(): any {
const trustedUsers: Array<any> = []; const trustedUsers: Array<any> = [];
this.mutableKeyStore.getPublicKeys().forEach(key => trustedUsers.push(key.users[0].userId)); this.mutableKeyStore.getPublicKeys().forEach((key) => trustedUsers.push(key.users[0].userId));
return trustedUsers; return trustedUsers;
} }
async getPublicKeys(): Promise<any> { async getPublicKeys(): Promise<any> {
return await fetch(environment.publicKeysUrl) return await fetch(environment.publicKeysUrl).then((res) => {
.then(res => { if (!res.ok) {
if (!res.ok) { // TODO does angular recommend an error interface?
// TODO does angular recommend an error interface? throw Error(`${res.statusText} - ${res.status}`);
throw Error(`${res.statusText} - ${res.status}`); }
} return res.text();
return res.text(); });
});
} }
getPrivateKey(): any { getPrivateKey(): any {
return this.mutableKeyStore.getPrivateKey(); return this.mutableKeyStore.getPrivateKey();
} }
} }

View File

@ -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);
}); });

View File

@ -1,14 +1,14 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {Settings} from '@app/_models'; import { Settings } from '@app/_models';
import {TransactionHelper} from 'cic-client'; import { TransactionHelper } from 'cic-client';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {TransactionService} from '@app/_services/transaction.service'; import { TransactionService } from '@app/_services/transaction.service';
import {environment} from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import {LoggingService} from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
import {RegistryService} from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class BlockSyncService { export class BlockSyncService {
readyStateTarget: number = 2; readyStateTarget: number = 2;
@ -17,8 +17,8 @@ export class BlockSyncService {
constructor( constructor(
private transactionService: TransactionService, private transactionService: TransactionService,
private loggingService: LoggingService, private loggingService: LoggingService,
private registryService: RegistryService, private registryService: RegistryService
) { } ) {}
blockSync(address: string = null, offset: number = 0, limit: number = 100): void { blockSync(address: string = null, offset: number = 0, limit: number = 100): void {
this.transactionService.resetTransactionsList(); this.transactionService.resetTransactionsList();
@ -43,7 +43,14 @@ 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: Worker = new Worker('./../assets/js/block-sync/head.js'); const wHeadSync: Worker = new Worker('./../assets/js/block-sync/head.js');
@ -54,13 +61,19 @@ 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);
});
} }
} }
} }
@ -81,7 +94,14 @@ export class BlockSyncService {
}); });
} }
async scan(settings: Settings, lo: number, hi: number, bloomBlockBytes: Uint8Array, bloomBlocktxBytes: Uint8Array, bloomRounds: any): Promise<void> { async scan(
settings: Settings,
lo: number,
hi: number,
bloomBlockBytes: Uint8Array,
bloomBlocktxBytes: Uint8Array,
bloomRounds: any
): Promise<void> {
const w: Worker = new Worker('./../assets/js/block-sync/ondemand.js'); 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);
@ -90,10 +110,7 @@ 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,
}); });
} }
@ -101,12 +118,19 @@ export class BlockSyncService {
fetcher(settings: Settings, transactionsInfo: any): void { fetcher(settings: Settings, transactionsInfo: any): void {
const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter); const blockFilterBinstr: string = window.atob(transactionsInfo.block_filter);
const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length); const bOne: Uint8Array = new Uint8Array(blockFilterBinstr.length);
bOne.map((e, i, v) => v[i] = blockFilterBinstr.charCodeAt(i)); bOne.map((e, i, v) => (v[i] = blockFilterBinstr.charCodeAt(i)));
const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter); const blocktxFilterBinstr: string = window.atob(transactionsInfo.blocktx_filter);
const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length); const bTwo: Uint8Array = new Uint8Array(blocktxFilterBinstr.length);
bTwo.map((e, i, v) => v[i] = blocktxFilterBinstr.charCodeAt(i)); bTwo.map((e, i, v) => (v[i] = blocktxFilterBinstr.charCodeAt(i)));
settings.scanFilter(settings, transactionsInfo.low, transactionsInfo.high, bOne, bTwo, transactionsInfo.filter_rounds); settings.scanFilter(
settings,
transactionsInfo.low,
transactionsInfo.high,
bOne,
bTwo,
transactionsInfo.filter_rounds
);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,26 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {BehaviorSubject, Observable} from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import {environment} from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import {Envelope, User} from 'cic-client-meta'; import { Envelope, User } from 'cic-client-meta';
import {UserService} from '@app/_services/user.service'; import { UserService } from '@app/_services/user.service';
import { Keccak } from 'sha3'; import { Keccak } from 'sha3';
import { utils } from 'ethers'; import { utils } from 'ethers';
import {add0x, fromHex, strip0x, toHex} from '@src/assets/js/ethtx/dist/hex'; import { add0x, fromHex, strip0x, toHex } from '@src/assets/js/ethtx/dist/hex';
import {Tx} from '@src/assets/js/ethtx/dist'; import { Tx } from '@src/assets/js/ethtx/dist';
import {toValue} from '@src/assets/js/ethtx/dist/tx'; import { toValue } from '@src/assets/js/ethtx/dist/tx';
import * as secp256k1 from 'secp256k1'; import * as secp256k1 from 'secp256k1';
import {AuthService} from '@app/_services/auth.service'; import { AuthService } from '@app/_services/auth.service';
import {defaultAccount} from '@app/_models'; import { defaultAccount } from '@app/_models';
import {LoggingService} from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {CICRegistry} from 'cic-client'; import { CICRegistry } from 'cic-client';
import {RegistryService} from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
import Web3 from 'web3'; import Web3 from 'web3';
const vCard = require('vcard-parser'); const vCard = require('vcard-parser');
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class TransactionService { export class TransactionService {
transactions: any[] = []; transactions: any[] = [];
@ -35,7 +35,7 @@ export class TransactionService {
private authService: AuthService, private authService: AuthService,
private userService: UserService, private userService: UserService,
private loggingService: LoggingService, private loggingService: LoggingService,
private registryService: RegistryService, private registryService: RegistryService
) { ) {
this.web3 = this.registryService.getWeb3(); this.web3 = this.registryService.getWeb3();
this.registry = registryService.getRegistry(); this.registry = registryService.getRegistry();
@ -51,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);
} }
@ -100,19 +122,29 @@ 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.getContractAddressByName('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);
@ -122,7 +154,7 @@ export class TransactionService {
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);

View File

@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { UserService } from '@app/_services/user.service'; import { UserService } from '@app/_services/user.service';
import {HttpClient} from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('UserService', () => { describe('UserService', () => {
let httpClient: HttpClient; let httpClient: HttpClient;
@ -11,7 +11,7 @@ describe('UserService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [HttpClientTestingModule] imports: [HttpClientTestingModule],
}); });
httpClient = TestBed.inject(HttpClient); httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController); httpTestingController = TestBed.inject(HttpTestingController);
@ -22,13 +22,33 @@ describe('UserService', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
it('should return user for available id', () => {
expect(service.getAccountById(1)).toEqual({
id: 1,
name: 'John Doe',
phone: '+25412345678',
address: '0xc86ff893ac40d3950b4d5f94a9b837258b0a9865',
type: 'user',
created: '08/16/2020',
balance: '12987',
failedPinAttempts: 1,
status: 'approved',
bio: 'Bodaboda',
gender: 'male'
});
});
it('should not return user for unavailable id', () => {
expect(service.getAccountById(9999999999)).toBeUndefined();
});
it('should return action for available id', () => { it('should return action for available id', () => {
expect(service.getActionById('1')).toEqual({ expect(service.getActionById('1')).toEqual({
id: 1, id: 1,
user: 'Tom', user: 'Tom',
role: 'enroller', role: 'enroller',
action: 'Disburse RSV 100', action: 'Disburse RSV 100',
approval: false approval: false,
}); });
}); });
@ -43,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,
}); });
}); });
@ -54,7 +74,7 @@ describe('UserService', () => {
user: 'Christine', user: 'Christine',
role: 'admin', role: 'admin',
action: 'Change user phone number', action: 'Change user phone number',
approval: false approval: false,
}); });
}); });
}); });

View File

@ -1,32 +1,34 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {BehaviorSubject, Observable, Subject} 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, Phone, Syncable, User} from 'cic-client-meta'; import { ArgPair, Envelope, Phone, Syncable, User } from 'cic-client-meta';
import {AccountDetails} from '@app/_models'; import { AccountDetails } from '@app/_models';
import {LoggingService} from '@app/_services/logging.service'; import { LoggingService } from '@app/_services/logging.service';
import {TokenService} from '@app/_services/token.service'; import { TokenService } from '@app/_services/token.service';
import {AccountIndex} from '@app/_eth'; import { AccountIndex } from '@app/_eth';
import {MutableKeyStore, PGPSigner, Signer} from '@app/_pgp'; import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp';
import {RegistryService} from '@app/_services/registry.service'; import { RegistryService } from '@app/_services/registry.service';
import {CICRegistry} from 'cic-client'; import { CICRegistry } from 'cic-client';
import {AuthService} from '@app/_services/auth.service'; import { AuthService } from '@app/_services/auth.service';
import {personValidation, vcardValidation} from '@app/_helpers'; import { personValidation, vcardValidation } from '@app/_helpers';
import {add0x} from '@src/assets/js/ethtx/dist/hex'; import { add0x } from '@src/assets/js/ethtx/dist/hex';
const vCard = require('vcard-parser'); const vCard = require('vcard-parser');
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root',
}) })
export class UserService { export class UserService {
headers: HttpHeaders = new HttpHeaders({'x-cic-automerge': 'client'}); headers: HttpHeaders = new HttpHeaders({ 'x-cic-automerge': 'client' });
keystore: MutableKeyStore; keystore: MutableKeyStore;
signer: Signer; signer: Signer;
registry: CICRegistry; registry: CICRegistry;
accounts: Array<AccountDetails> = []; accounts: Array<AccountDetails> = [];
private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<Array<AccountDetails>>(this.accounts); private accountsList: BehaviorSubject<Array<AccountDetails>> = new BehaviorSubject<
Array<AccountDetails>
>(this.accounts);
accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable(); accountsSubject: Observable<Array<AccountDetails>> = this.accountsList.asObservable();
actions: Array<any> = []; actions: Array<any> = [];
@ -38,7 +40,7 @@ export class UserService {
private loggingService: LoggingService, private loggingService: LoggingService,
private tokenService: TokenService, private tokenService: TokenService,
private registryService: RegistryService, private registryService: RegistryService,
private authService: AuthService, private authService: AuthService
) { ) {
this.authService.init().then(() => { this.authService.init().then(() => {
this.keystore = authService.mutableKeyStore; this.keystore = authService.mutableKeyStore;
@ -50,28 +52,38 @@ export class UserService {
resetPin(phone: string): Observable<any> { resetPin(phone: string): Observable<any> {
const params: HttpParams = new HttpParams().set('phoneNumber', phone); const params: HttpParams = new HttpParams().set('phoneNumber', phone);
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
} }
getAccountStatus(phone: string): Observable<any> { getAccountStatus(phone: string): Observable<any> {
const params: HttpParams = new HttpParams().set('phoneNumber', phone); const params: HttpParams = new HttpParams().set('phoneNumber', phone);
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params}); return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
} }
getLockedAccounts(offset: number, limit: number): Observable<any> { getLockedAccounts(offset: number, limit: number): Observable<any> {
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`); return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
} }
async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string, async changeAccountInfo(
businessCategory: string, userLocation: string, location: string, locationType: string 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> {
const accountInfo: any = { const accountInfo: any = {
vcard: { vcard: {
fn: [{}], fn: [{}],
n: [{}], n: [{}],
tel: [{}] tel: [{}],
}, },
location: {} location: {},
}; };
accountInfo.vcard.fn[0].value = name; accountInfo.vcard.fn[0].value = name;
accountInfo.vcard.n[0].value = name.split(' '); accountInfo.vcard.n[0].value = name.split(' ');
@ -87,33 +99,52 @@ export class UserService {
await vcardValidation(accountInfo.vcard); await vcardValidation(accountInfo.vcard);
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard)); accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
const accountKey: string = await User.toKey(address); const accountKey: string = await User.toKey(address);
this.getAccountDetailsFromMeta(accountKey).pipe(first()).subscribe(async res => { this.getAccountDetailsFromMeta(accountKey)
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); .pipe(first())
const update: Array<ArgPair> = []; .subscribe(
for (const prop in accountInfo) { async (res) => {
update.push(new ArgPair(prop, accountInfo[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 personValidation(syncableAccount.m.data); update.push(new ArgPair(prop, accountInfo[prop]));
await this.updateMeta(syncableAccount, accountKey, this.headers); }
}, async error => { syncableAccount.update(update, 'client-branch');
this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error}); await personValidation(syncableAccount.m.data);
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo); 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: Envelope = await this.wrap(syncableAccount , this.signer); syncableAccount: Syncable,
accountKey: string,
headers: HttpHeaders
): Promise<any> {
const envelope: Envelope = await this.wrap(syncableAccount, this.signer);
const reqBody: string = envelope.toJSON(); const reqBody: string = envelope.toJSON();
this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => { this.httpClient
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); .put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
}); .pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
});
} }
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): Observable<any> { getActionById(id: string): Observable<any> {
@ -148,44 +179,60 @@ export class UserService {
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> { async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
this.resetAccountsList(); this.resetAccountsList();
const accountIndexAddress: string = await this.registry.getContractAddressByName('AccountRegistry'); const accountIndexAddress: string = await this.registry.getContractAddressByName(
'AccountRegistry'
);
const accountIndexQuery = new AccountIndex(accountIndexAddress); const accountIndexQuery = new AccountIndex(accountIndexAddress);
const accountAddresses: Array<string> = await accountIndexQuery.last(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)) {
await this.getAccountByAddress(accountAddress, limit); await this.getAccountByAddress(accountAddress, limit);
} }
} }
async getAccountByAddress(accountAddress: string, limit: number = 100): Promise<Observable<AccountDetails>> { async getAccountByAddress(
let accountSubject: Subject<any> = new Subject<any>(); accountAddress: string,
this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress))).pipe(first()).subscribe(async res => { limit: number = 100
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); ): Promise<Observable<AccountDetails>> {
const accountInfo = account.m.data; const accountSubject: Subject<any> = new Subject<any>();
await personValidation(accountInfo); this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]); .pipe(first())
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); .subscribe(async (res) => {
await vcardValidation(accountInfo.vcard); const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
this.accounts.unshift(accountInfo); const accountInfo = account.m.data;
if (this.accounts.length > limit) { await personValidation(accountInfo);
this.accounts.length = limit; accountInfo.balance = await this.tokenService.getTokenBalance(
} accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
this.accountsList.next(this.accounts); );
accountSubject.next(accountInfo); accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
}); await vcardValidation(accountInfo.vcard);
this.accounts.unshift(accountInfo);
if (this.accounts.length > limit) {
this.accounts.length = limit;
}
this.accountsList.next(this.accounts);
accountSubject.next(accountInfo);
});
return accountSubject.asObservable(); return accountSubject.asObservable();
} }
async getAccountByPhone(phoneNumber: string, limit: number = 100): Promise<Observable<AccountDetails>> { async getAccountByPhone(
let accountSubject: Subject<any> = new Subject<any>(); phoneNumber: string,
this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber)).pipe(first()).subscribe(async res => { limit: number = 100
const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); ): Promise<Observable<AccountDetails>> {
const address: string = response.m.data; const accountSubject: Subject<any> = new Subject<any>();
const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit); this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber))
account.subscribe(result => { .pipe(first())
accountSubject.next(result); .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(); return accountSubject.asObservable();
} }
@ -194,7 +241,9 @@ export class UserService {
this.accountsList.next(this.accounts); this.accountsList.next(this.accounts);
} }
searchAccountByName(name: string): any { return; } searchAccountByName(name: string): any {
return;
}
getCategories(): Observable<any> { getCategories(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/categories`); return this.httpClient.get(`${environment.cicMetaUrl}/categories`);

View File

@ -1,17 +1,23 @@
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] }),
],
exports: [RouterModule],
}) })
export class AppRoutingModule { } export class AppRoutingModule {}

View File

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

View File

@ -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();
}); });

View File

@ -1,14 +1,20 @@
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;
@ -18,7 +24,8 @@ export class AppComponent {
private authService: AuthService, private authService: AuthService,
private transactionService: TransactionService, private transactionService: TransactionService,
private loggingService: LoggingService, private loggingService: LoggingService,
private errorDialogService: ErrorDialogService private errorDialogService: ErrorDialogService,
private swUpdate: SwUpdate
) { ) {
(async () => { (async () => {
try { try {
@ -31,14 +38,26 @@ export class AppComponent {
const publicKeys = await this.authService.getPublicKeys(); const publicKeys = await this.authService.getPublicKeys();
await this.authService.mutableKeyStore.importPublicKey(publicKeys); await this.authService.mutableKeyStore.importPublicKey(publicKeys);
} catch (error) { } catch (error) {
this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'}); 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 🦗? // 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: HTMLElement = document.getElementById('sidebar'); const sidebar: HTMLElement = document.getElementById('sidebar');

View File

@ -1,27 +1,23 @@
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 { DataTablesModule } from 'angular-datatables';
MockBackendProvider, import { SharedModule } from '@app/shared/shared.module';
} from '@app/_helpers'; import { MatTableModule } from '@angular/material/table';
import {DataTablesModule} from 'angular-datatables'; import { AuthGuard } from '@app/_guards';
import {SharedModule} from '@app/shared/shared.module'; import { LoggerModule } from 'ngx-logger';
import {MatTableModule} from '@angular/material/table'; import { environment } from '@src/environments/environment';
import {AuthGuard} from '@app/_guards'; import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors';
import {LoggerModule} from 'ngx-logger'; import { MutablePgpKeyStore } from '@app/_pgp';
import {environment} from '@src/environments/environment'; import { ServiceWorkerModule } from '@angular/service-worker';
import {ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor} from '@app/_interceptors';
import {MutablePgpKeyStore} from '@app/_pgp';
@NgModule({ @NgModule({
declarations: [ declarations: [AppComponent],
AppComponent
],
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
@ -34,8 +30,9 @@ import {MutablePgpKeyStore} from '@app/_pgp';
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 +44,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 {}

View File

@ -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', () => {

View File

@ -1,7 +1,7 @@
import {Directive, ElementRef, Input, Renderer2} from '@angular/core'; import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
@Directive({ @Directive({
selector: '[appPasswordToggle]' selector: '[appPasswordToggle]',
}) })
export class PasswordToggleDirective { export class PasswordToggleDirective {
@Input() @Input()
@ -10,10 +10,7 @@ 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();
}); });

View File

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

View File

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

View File

@ -1,14 +1,14 @@
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {CustomErrorStateMatcher} from '@app/_helpers'; import { CustomErrorStateMatcher } from '@app/_helpers';
import {AuthService} from '@app/_services'; import { AuthService } from '@app/_services';
import {Router} from '@angular/router'; import { Router } from '@angular/router';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
templateUrl: './auth.component.html', templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss'], styleUrls: ['./auth.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AuthComponent implements OnInit { export class AuthComponent implements OnInit {
keyForm: FormGroup; keyForm: FormGroup;
@ -20,7 +20,7 @@ export class AuthComponent implements OnInit {
private authService: AuthService, private authService: AuthService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router private router: Router
) { } ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.keyForm = this.formBuilder.group({ this.keyForm = this.formBuilder.group({
@ -33,12 +33,16 @@ export class AuthComponent implements OnInit {
// } // }
} }
get keyFormStub(): any { return this.keyForm.controls; } get keyFormStub(): any {
return this.keyForm.controls;
}
async onSubmit(): Promise<void> { async onSubmit(): Promise<void> {
this.submitted = true; this.submitted = true;
if (this.keyForm.invalid) { return; } if (this.keyForm.invalid) {
return;
}
this.loading = true; this.loading = true;
await this.authService.setKey(this.keyFormStub.key.value); await this.authService.setKey(this.keyFormStub.key.value);
@ -46,11 +50,11 @@ export class AuthComponent implements OnInit {
} }
login(): void { login(): void {
// TODO check if we have privatekey // TODO check if we have privatekey
// Send us to home if we have a private key // Send us to home if we have a private key
// talk to meta somehow // talk to meta somehow
// in the error interceptor if 401/403 handle it // in the error interceptor if 401/403 handle it
// if 200 go /home // if 200 go /home
if (this.authService.getPrivateKey()) { if (this.authService.getPrivateKey()) {
this.router.navigate(['/home']); this.router.navigate(['/home']);
} }

View File

@ -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 {}

View File

@ -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);
}); });

View File

@ -1,37 +1,50 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, 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 {copyToClipboard, CustomErrorStateMatcher, exportCsv} from '@app/_helpers'; import { MatPaginator } from '@angular/material/paginator';
import {MatSnackBar} from '@angular/material/snack-bar'; import { MatSort } from '@angular/material/sort';
import {add0x, strip0x} from '@src/assets/js/ethtx/dist/hex'; import {
import {environment} from '@src/environments/environment'; BlockSyncService,
import {AccountDetails, AreaName, AreaType, Category, Transaction} from '@app/_models'; 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: Array<string> = ['sender', 'recipient', 'value', 'created', 'type']; transactionsDisplayedColumns: Array<string> = ['sender', 'recipient', 'value', 'created', 'type'];
transactionsDefaultPageSize: number = 10; transactionsDefaultPageSize: number = 10;
transactionsPageSizeOptions: Array<number> = [10, 20, 50, 100]; transactionsPageSizeOptions: Array<number> = [10, 20, 50, 100];
@ViewChild('TransactionTablePaginator', {static: true}) transactionTablePaginator: MatPaginator; @ViewChild('TransactionTablePaginator', { static: true }) transactionTablePaginator: MatPaginator;
@ViewChild('TransactionTableSort', {static: true}) transactionTableSort: MatSort; @ViewChild('TransactionTableSort', { static: true }) transactionTableSort: MatSort;
userDataSource: MatTableDataSource<any>; userDataSource: MatTableDataSource<any>;
userDisplayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location']; userDisplayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location'];
usersDefaultPageSize: number = 10; usersDefaultPageSize: number = 10;
usersPageSizeOptions: Array<number> = [10, 20, 50, 100]; usersPageSizeOptions: Array<number> = [10, 20, 50, 100];
@ViewChild('UserTablePaginator', {static: true}) userTablePaginator: MatPaginator; @ViewChild('UserTablePaginator', { static: true }) userTablePaginator: MatPaginator;
@ViewChild('UserTableSort', {static: true}) userTableSort: MatSort; @ViewChild('UserTableSort', { static: true }) userTableSort: MatSort;
accountInfoForm: FormGroup; accountInfoForm: FormGroup;
account: AccountDetails; account: AccountDetails;
@ -63,7 +76,7 @@ export class AccountDetailsComponent implements OnInit {
private loggingService: LoggingService, private loggingService: LoggingService,
private blockSyncService: BlockSyncService, private blockSyncService: BlockSyncService,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
private snackBar: MatSnackBar, private snackBar: MatSnackBar
) { ) {
this.accountInfoForm = this.formBuilder.group({ this.accountInfoForm = this.formBuilder.group({
name: ['', Validators.required], name: ['', Validators.required],
@ -79,49 +92,70 @@ export class AccountDetailsComponent implements OnInit {
}); });
this.route.paramMap.subscribe(async (params: Params) => { this.route.paramMap.subscribe(async (params: Params) => {
this.accountAddress = add0x(params.get('id')); this.accountAddress = add0x(params.get('id'));
this.bloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions'; this.bloxbergLink =
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(async res => { 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
if (res !== undefined) { (await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
this.account = res; async (res) => {
this.cdr.detectChanges(); if (res !== undefined) {
this.loggingService.sendInfoLevelMessage(this.account); this.account = res;
// this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first()) this.cdr.detectChanges();
// .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,
} else { locationType: this.account.location.area_type,
alert('Account not found!'); });
} else {
alert('Account not found!');
}
} }
}); );
this.blockSyncService.blockSync(this.accountAddress); this.blockSyncService.blockSync(this.accountAddress);
}); });
this.userService.getCategories().pipe(first()).subscribe(res => this.categories = res); this.userService
this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res); .getCategories()
this.locationService.getAreaTypes().pipe(first()).subscribe(res => this.areaTypes = res); .pipe(first())
this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res); .subscribe((res) => (this.categories = res));
this.userService.getTransactionTypes().pipe(first()).subscribe(res => this.transactionsTypes = res); this.locationService
this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res); .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;
@ -142,14 +176,20 @@ export class AccountDetailsComponent implements OnInit {
} }
viewAccount(account): void { viewAccount(account): void {
this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); this.router.navigateByUrl(
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} }
get accountInfoFormStub(): any { return this.accountInfoForm.controls; } get accountInfoFormStub(): any {
return this.accountInfoForm.controls;
}
async saveInfo(): Promise<void> { 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.accountAddress, this.accountAddress,
this.accountInfoFormStub.name.value, this.accountInfoFormStub.name.value,
@ -168,31 +208,40 @@ export class AccountDetailsComponent implements OnInit {
filterAccounts(): void { filterAccounts(): void {
if (this.accountsType === 'all') { if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe(accounts => { this.userService.accountsSubject.subscribe((accounts) => {
this.userDataSource.data = accounts; this.userDataSource.data = accounts;
this.accounts = accounts; this.accounts = accounts;
}); });
} else { } else {
this.userDataSource.data = this.accounts.filter(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.vcard.tel[0].value).pipe(first()).subscribe(res => { return;
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); }
}); this.userService
.resetPin(this.account.vcard.tel[0].value)
.pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
});
} }
downloadCsv(data: any, filename: string): void { downloadCsv(data: any, filename: string): void {
@ -201,7 +250,9 @@ export class AccountDetailsComponent implements OnInit {
copyAddress(): void { copyAddress(): void {
if (copyToClipboard(this.accountAddress)) { if (copyToClipboard(this.accountAddress)) {
this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', { duration: 3000 }); this.snackBar.open(this.accountAddress + ' copied successfully!', 'Close', {
duration: 3000,
});
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,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);
}); });

View File

@ -1,20 +1,20 @@
import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
import {MatTableDataSource} from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import {MatPaginator} from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import {LoggingService, UserService} from '@app/_services'; import { LoggingService, UserService } from '@app/_services';
import {Router} from '@angular/router'; import { Router } from '@angular/router';
import {exportCsv} from '@app/_helpers'; import { exportCsv } from '@app/_helpers';
import {strip0x} from '@src/assets/js/ethtx/dist/hex'; import { strip0x } from '@src/assets/js/ethtx/dist/hex';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {environment} from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import {AccountDetails} from '@app/_models'; import { AccountDetails } from '@app/_models';
@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>;
@ -32,21 +32,23 @@ export class AccountsComponent implements OnInit {
private userService: UserService, private userService: UserService,
private loggingService: LoggingService, private loggingService: LoggingService,
private router: Router private router: Router
) ) {
{
(async () => { (async () => {
try { try {
// TODO it feels like this should be in the onInit handler // 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); 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;
@ -59,17 +61,19 @@ export class AccountsComponent implements OnInit {
} }
async viewAccount(account): Promise<void> { async viewAccount(account): Promise<void> {
await this.router.navigateByUrl(`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`); await this.router.navigateByUrl(
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
);
} }
filterAccounts(): void { filterAccounts(): void {
if (this.accountsType === 'all') { if (this.accountsType === 'all') {
this.userService.accountsSubject.subscribe(accounts => { this.userService.accountsSubject.subscribe((accounts) => {
this.dataSource.data = accounts; this.dataSource.data = accounts;
this.accounts = accounts; this.accounts = accounts;
}); });
} else { } else {
this.dataSource.data = this.accounts.filter(account => account.type === this.accountsType); this.dataSource.data = this.accounts.filter((account) => account.type === this.accountsType);
} }
} }

View File

@ -3,35 +3,34 @@ 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 { DataTablesModule } from 'angular-datatables';
import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component'; import { CreateAccountComponent } from '@pages/accounts/create-account/create-account.component';
import {MatTableModule} from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import {MatSortModule} from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import {MatCheckboxModule} from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import {MatPaginatorModule} from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import {MatInputModule} from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import {MatButtonModule} from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import {MatCardModule} from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import {MatSelectModule} from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import {TransactionsModule} from '@pages/transactions/transactions.module'; import { TransactionsModule } from '@pages/transactions/transactions.module';
import {MatTabsModule} from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import {MatRippleModule} from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import {ReactiveFormsModule} from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { AccountSearchComponent } from './account-search/account-search.component'; import { AccountSearchComponent } from './account-search/account-search.component';
import {MatSnackBarModule} from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
@NgModule({ @NgModule({
declarations: [ declarations: [
AccountsComponent, AccountsComponent,
AccountDetailsComponent, AccountDetailsComponent,
CreateAccountComponent, CreateAccountComponent,
AccountSearchComponent AccountSearchComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -54,6 +53,6 @@ import {MatSnackBarModule} from '@angular/material/snack-bar';
MatProgressSpinnerModule, MatProgressSpinnerModule,
ReactiveFormsModule, ReactiveFormsModule,
MatSnackBarModule, MatSnackBarModule,
] ],
}) })
export class AccountsModule { } export class AccountsModule {}

View File

@ -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(() => {

View File

@ -1,15 +1,15 @@
import {ChangeDetectionStrategy, Component, OnInit} from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {LocationService, UserService} from '@app/_services'; import { LocationService, UserService } from '@app/_services';
import {CustomErrorStateMatcher} from '@app/_helpers'; import { CustomErrorStateMatcher } from '@app/_helpers';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {AreaName, Category} from '@app/_models'; import { AreaName, Category } from '@app/_models';
@Component({ @Component({
selector: 'app-create-account', selector: 'app-create-account',
templateUrl: './create-account.component.html', templateUrl: './create-account.component.html',
styleUrls: ['./create-account.component.scss'], styleUrls: ['./create-account.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class CreateAccountComponent implements OnInit { export class CreateAccountComponent implements OnInit {
createForm: FormGroup; createForm: FormGroup;
@ -24,7 +24,7 @@ export class CreateAccountComponent implements OnInit {
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private locationService: LocationService, private locationService: LocationService,
private userService: UserService private userService: UserService
) { } ) {}
ngOnInit(): void { ngOnInit(): void {
this.createForm = this.formBuilder.group({ this.createForm = this.formBuilder.group({
@ -37,19 +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.userService.getCategories().pipe(first()).subscribe(res => this.categories = res); this.userService
this.locationService.getAreaNames().pipe(first()).subscribe(res => this.areaNames = res); .getCategories()
this.userService.getAccountTypes().pipe(first()).subscribe(res => this.accountTypes = res); .pipe(first())
this.userService.getGenders().pipe(first()).subscribe(res => this.genders = res); .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;
} }
} }

View File

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

View File

@ -1,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,
}); });
}); });
}); });

View File

@ -1,12 +1,12 @@
import {ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
import {MatTableDataSource} from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import {MatPaginator} from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import {LoggingService, UserService} from '@app/_services'; import { LoggingService, UserService } from '@app/_services';
import {animate, state, style, transition, trigger} from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import {first} from 'rxjs/operators'; import { first } from 'rxjs/operators';
import {exportCsv} from '@app/_helpers'; import { exportCsv } from '@app/_helpers';
import {Action} from '../../_models'; import { Action } from '../../_models';
@Component({ @Component({
selector: 'app-admin', selector: 'app-admin',
@ -15,11 +15,11 @@ import {Action} from '../../_models';
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: [ animations: [
trigger('detailExpand', [ trigger('detailExpand', [
state('collapsed', style({height: '0px', minHeight: 0, visibility: 'hidden'})), state('collapsed', style({ height: '0px', minHeight: 0, visibility: 'hidden' })),
state('expanded', style({height: '*', visibility: 'visible'})), state('expanded', style({ height: '*', visibility: 'visible' })),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
]) ]),
] ],
}) })
export class AdminComponent implements OnInit { export class AdminComponent implements OnInit {
dataSource: MatTableDataSource<any>; dataSource: MatTableDataSource<any>;
@ -30,12 +30,9 @@ export class AdminComponent implements OnInit {
@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;
@ -43,8 +40,7 @@ export class AdminComponent implements OnInit {
}); });
} }
ngOnInit(): void { ngOnInit(): void {}
}
doFilter(value: string): void { doFilter(value: string): void {
this.dataSource.filter = value.trim().toLocaleLowerCase(); this.dataSource.filter = value.trim().toLocaleLowerCase();
@ -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();
} }

View File

@ -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 {}

View File

@ -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 {}

View File

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

View File

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

View File

@ -3,27 +3,26 @@ 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 { 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, ChartsModule,
MatButtonModule, MatButtonModule,
MatFormFieldModule, MatFormFieldModule,
MatSelectModule, MatSelectModule,
MatInputModule, MatInputModule,
MatCardModule MatCardModule,
] ],
}) })
export class PagesModule { } export class PagesModule {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,44 +4,41 @@ import { CommonModule } from '@angular/common';
import { TransactionsRoutingModule } from '@pages/transactions/transactions-routing.module'; import { TransactionsRoutingModule } from '@pages/transactions/transactions-routing.module';
import { TransactionsComponent } from '@pages/transactions/transactions.component'; import { TransactionsComponent } from '@pages/transactions/transactions.component';
import { TransactionDetailsComponent } from '@pages/transactions/transaction-details/transaction-details.component'; import { TransactionDetailsComponent } from '@pages/transactions/transaction-details/transaction-details.component';
import {DataTablesModule} from 'angular-datatables'; import { DataTablesModule } from 'angular-datatables';
import {SharedModule} from '@app/shared/shared.module'; import { SharedModule } from '@app/shared/shared.module';
import {MatTableModule} from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import {MatCheckboxModule} from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import {MatPaginatorModule} from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import {MatSortModule} from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import {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 {MatButtonModule} from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import {MatSelectModule} from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import {MatCardModule} from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import {MatRippleModule} from '@angular/material/core'; import { MatRippleModule } from '@angular/material/core';
import {MatSnackBarModule} from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
@NgModule({ @NgModule({
declarations: [TransactionsComponent, TransactionDetailsComponent], declarations: [TransactionsComponent, TransactionDetailsComponent],
exports: [ exports: [TransactionDetailsComponent],
TransactionDetailsComponent imports: [
CommonModule,
TransactionsRoutingModule,
DataTablesModule,
SharedModule,
MatTableModule,
MatCheckboxModule,
MatPaginatorModule,
MatSortModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatSelectModule,
MatCardModule,
MatRippleModule,
MatSnackBarModule,
], ],
imports: [
CommonModule,
TransactionsRoutingModule,
DataTablesModule,
SharedModule,
MatTableModule,
MatCheckboxModule,
MatPaginatorModule,
MatSortModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatIconModule,
MatSelectModule,
MatCardModule,
MatRippleModule,
MatSnackBarModule,
]
}) })
export class TransactionsModule { } export class TransactionsModule {}

View File

@ -1,8 +1,10 @@
import { MenuSelectionDirective } from '@app/shared/_directives/menu-selection.directive'; import { MenuSelectionDirective } from '@app/shared/_directives/menu-selection.directive';
import {ElementRef, Renderer2} from '@angular/core'; import { ElementRef, Renderer2 } from '@angular/core';
describe('MenuSelectionDirective', () => { describe('MenuSelectionDirective', () => {
// tslint:disable-next-line:prefer-const
let elementRef: ElementRef; let elementRef: ElementRef;
// tslint:disable-next-line:prefer-const
let renderer: Renderer2; let renderer: Renderer2;
beforeEach(() => { beforeEach(() => {

View File

@ -1,14 +1,10 @@
import {Directive, ElementRef, Renderer2} from '@angular/core'; import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({ @Directive({
selector: '[appMenuSelection]' selector: '[appMenuSelection]',
}) })
export class MenuSelectionDirective { export class MenuSelectionDirective {
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
constructor(
private elementRef: ElementRef,
private renderer: Renderer2
) {
this.renderer.listen(this.elementRef.nativeElement, 'click', () => { this.renderer.listen(this.elementRef.nativeElement, 'click', () => {
const mediaQuery = window.matchMedia('(max-width: 768px)'); const mediaQuery = window.matchMedia('(max-width: 768px)');
if (mediaQuery.matches) { if (mediaQuery.matches) {

View File

@ -1,8 +1,10 @@
import { MenuToggleDirective } from '@app/shared/_directives/menu-toggle.directive'; import { MenuToggleDirective } from '@app/shared/_directives/menu-toggle.directive';
import {ElementRef, Renderer2} from '@angular/core'; import { ElementRef, Renderer2 } from '@angular/core';
describe('MenuToggleDirective', () => { describe('MenuToggleDirective', () => {
// tslint:disable-next-line:prefer-const
let elementRef: ElementRef; let elementRef: ElementRef;
// tslint:disable-next-line:prefer-const
let renderer: Renderer2; let renderer: Renderer2;
it('should create an instance', () => { it('should create an instance', () => {
const directive = new MenuToggleDirective(elementRef, renderer); const directive = new MenuToggleDirective(elementRef, renderer);

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