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

@ -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) {
@ -47,5 +48,4 @@ export class RoleGuard implements CanActivate {
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

@ -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,6 +1,6 @@
// 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.

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)) {
@ -35,6 +35,4 @@ function exportCsv(arrayData: Array<any>, filename: string, delimiter: string =
} }
/** @exports */ /** @exports */
export { export { exportCsv };
exportCsv
};

View File

@ -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,7 +7,8 @@ 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) =>
new Promise((resolve, reject) => {
const xhr: XMLHttpRequest = new XMLHttpRequest(); const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.addEventListener('load', (e) => { xhr.addEventListener('load', (e) => {
if (xhr.status === 200) { if (xhr.status === 200) {
@ -21,6 +22,4 @@ HttpGetter.prototype.get = (filename: string) => new Promise((resolve, reject) =
}); });
/** @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

@ -3,7 +3,8 @@ 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';
@ -12,7 +13,6 @@ 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,7 +29,9 @@ export class ErrorInterceptor implements HttpInterceptor {
} else { } else {
// The backend returned an unsuccessful response code. // The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong. // The response body may contain clues as to what went wrong.
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(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) {

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,7 +4,7 @@ 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';
@ -12,10 +12,7 @@ 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: [{ ];
fn: [
{
value: string; value: string;
}]; }
n: [{ ];
n: [
{
value: string[]; value: string[];
}]; }
tel: [{ ];
tel: [
{
meta: { meta: {
TYP: string[]; TYP: string[];
}, };
value: string; value: string;
}], }
version: [{ ];
version: [
{
value: string; value: string;
}]; }
];
}; };
} }
@ -75,31 +85,35 @@ const defaultAccount: AccountDetails = {
}, },
products: [], products: [],
vcard: { vcard: {
email: [{ email: [
{
value: '', value: '',
}], },
fn: [{ ],
fn: [
{
value: 'Sarafu Contract', value: 'Sarafu Contract',
}], },
n: [{ ],
n: [
{
value: ['Sarafu', 'Contract'], value: ['Sarafu', 'Contract'],
}], },
tel: [{ ],
tel: [
{
meta: { meta: {
TYP: [], TYP: [],
}, },
value: '', value: '',
}], },
version: [{ ],
version: [
{
value: '3.0', value: '3.0',
}], },
],
}, },
}; };
export { 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

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

View File

@ -32,7 +32,6 @@ interface MutableKeyStore extends KeyStore {
} }
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> {
@ -159,7 +166,4 @@ class MutablePgpKeyStore implements MutableKeyStore{
} }
} }
export { export { MutablePgpKeyStore, MutableKeyStore };
MutablePgpKeyStore,
MutableKeyStore
};

View File

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

View File

@ -9,7 +9,7 @@ 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;
@ -95,7 +95,6 @@ export class AuthService {
xhr.send(); xhr.send();
} }
login(): boolean { login(): boolean {
if (this.sessionToken !== undefined) { if (this.sessionToken !== undefined) {
try { try {
@ -114,26 +113,30 @@ export class AuthService {
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.challenge,
o.realm, o.realm,
environment.cicMetaUrl, environment.cicMetaUrl,
this.mutableKeyStore); 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.',
}); });
} }
} }
@ -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,18 +184,17 @@ 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}`);

View File

@ -9,9 +9,7 @@ describe('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

@ -8,7 +8,7 @@ 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,7 +17,7 @@ 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 {
@ -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,11 +61,17 @@ 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
.getAllTransactions(offset, limit)
.pipe(first())
.subscribe((res) => {
this.fetcher(settings, res); this.fetcher(settings, res);
}); });
} else { } else {
this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => { this.transactionService
.getAddressTransactions(address, offset, limit)
.pipe(first())
.subscribe((res) => {
this.fetcher(settings, res); 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

@ -3,14 +3,12 @@ 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

@ -5,13 +5,10 @@ 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

@ -2,7 +2,7 @@ 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

@ -5,13 +5,18 @@ 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

@ -7,21 +7,20 @@ 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());
}; };
} }

View File

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

@ -20,7 +20,7 @@ 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
.getAccountDetailsFromMeta(await User.toKey(transaction.from))
.pipe(first())
.subscribe(
(res) => {
transaction.sender = this.getAccountInfo(res.body); transaction.sender = this.getAccountInfo(res.body);
}, error => { },
(error) => {
transaction.sender = defaultAccount; transaction.sender = defaultAccount;
}); }
this.userService.getAccountDetailsFromMeta(await User.toKey(transaction.to)).pipe(first()).subscribe((res) => { );
this.userService
.getAccountDetailsFromMeta(await User.toKey(transaction.to))
.pipe(first())
.subscribe(
(res) => {
transaction.recipient = this.getAccountInfo(res.body); transaction.recipient = this.getAccountInfo(res.body);
}, error => { },
(error) => {
transaction.recipient = defaultAccount; transaction.recipient = defaultAccount;
}); }
);
} finally { } finally {
this.addTransaction(transaction, cacheSize); this.addTransaction(transaction, cacheSize);
} }
} }
async setConversion(conversion, cacheSize): Promise<void> { async setConversion(conversion, cacheSize): Promise<void> {
if (this.transactions.find(cachedTx => cachedTx.tx.txHash === conversion.tx.txHash)) { 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
.getAccountDetailsFromMeta(await User.toKey(conversion.trader))
.pipe(first())
.subscribe(
(res) => {
conversion.sender = conversion.recipient = this.getAccountInfo(res.body); conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
}, error => { },
(error) => {
conversion.sender = conversion.recipient = defaultAccount; conversion.sender = conversion.recipient = defaultAccount;
}); }
);
} finally { } finally {
this.addTransaction(conversion, cacheSize); this.addTransaction(conversion, cacheSize);
} }
@ -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);

View File

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

@ -17,7 +17,7 @@ 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' });
@ -26,7 +26,9 @@ export class UserService {
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;
@ -62,16 +64,26 @@ export class UserService {
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)
.pipe(first())
.subscribe(
async (res) => {
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const update: Array<ArgPair> = []; const update: Array<ArgPair> = [];
for (const prop in accountInfo) { for (const prop of Object.keys(accountInfo)) {
update.push(new ArgPair(prop, accountInfo[prop])); update.push(new ArgPair(prop, accountInfo[prop]));
} }
syncableAccount.update(update, 'client-branch'); syncableAccount.update(update, 'client-branch');
await personValidation(syncableAccount.m.data); await personValidation(syncableAccount.m.data);
await this.updateMeta(syncableAccount, accountKey, this.headers); await this.updateMeta(syncableAccount, accountKey, this.headers);
}, async error => { },
this.loggingService.sendErrorLevelMessage('Can\'t find account info in meta service', this, {error}); async (error) => {
this.loggingService.sendErrorLevelMessage(
'Cannot find account info in meta service',
this,
{ error }
);
const syncableAccount: Syncable = new Syncable(accountKey, accountInfo); const syncableAccount: Syncable = new Syncable(accountKey, accountInfo);
await this.updateMeta(syncableAccount, accountKey, this.headers); await this.updateMeta(syncableAccount, accountKey, this.headers);
}); }
);
return accountKey; return accountKey;
} }
async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> { async updateMeta(
syncableAccount: Syncable,
accountKey: string,
headers: HttpHeaders
): Promise<any> {
const envelope: Envelope = await this.wrap(syncableAccount, this.signer); const 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
.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody, { headers })
.pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
}); });
} }
getActions(): void { getActions(): void {
this.httpClient.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,22 +179,33 @@ 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
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
.pipe(first())
.subscribe(async (res) => {
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const accountInfo = account.m.data; const accountInfo = account.m.data;
await personValidation(accountInfo); await personValidation(accountInfo);
accountInfo.balance = await this.tokenService.getTokenBalance(accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]); accountInfo.balance = await this.tokenService.getTokenBalance(
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
);
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
await vcardValidation(accountInfo.vcard); await vcardValidation(accountInfo.vcard);
this.accounts.unshift(accountInfo); this.accounts.unshift(accountInfo);
@ -176,13 +218,18 @@ export class UserService {
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
): Promise<Observable<AccountDetails>> {
const accountSubject: Subject<any> = new Subject<any>();
this.getAccountDetailsFromMeta(await Phone.toKey(phoneNumber))
.pipe(first())
.subscribe(async (res) => {
const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap(); const response: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
const address: string = response.m.data; const address: string = response.m.data;
const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit); const account: Observable<AccountDetails> = await this.getAccountByAddress(address, limit);
account.subscribe(result => { account.subscribe((result) => {
accountSubject.next(result); accountSubject.next(result);
}); });
}); });
@ -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

@ -3,15 +3,21 @@ 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

@ -2,23 +2,19 @@ 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 {
AuthService,
ErrorDialogService,
LoggingService,
TransactionService,
} from '@app/_services';
import { catchError } from 'rxjs/operators'; 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

@ -5,10 +5,7 @@ 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,
MockBackendProvider,
} from '@app/_helpers';
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';
@ -17,11 +14,10 @@ import {LoggerModule} from 'ngx-logger';
import { environment } from '@src/environments/environment'; import { environment } from '@src/environments/environment';
import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors'; import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors';
import { MutablePgpKeyStore } from '@app/_pgp'; import { MutablePgpKeyStore } from '@app/_pgp';
import { ServiceWorkerModule } from '@angular/service-worker';
@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

@ -10,6 +10,6 @@ const routes: Routes = [
@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

@ -8,7 +8,7 @@ import {Router} from '@angular/router';
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;
@ -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);

View File

@ -11,7 +11,6 @@ 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],
imports: [ imports: [
@ -23,6 +22,6 @@ import {MatRippleModule} from '@angular/material/core';
MatInputModule, MatInputModule,
MatButtonModule, MatButtonModule,
MatRippleModule, MatRippleModule,
] ],
}) })
export class AuthModule {} export class AuthModule {}

View File

@ -7,7 +7,13 @@ 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,8 +1,21 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core'; import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
OnInit,
ViewChild,
} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table'; import { 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 {BlockSyncService, LocationService, LoggingService, TokenService, TransactionService, UserService} from '@app/_services'; import {
BlockSyncService,
LocationService,
LoggingService,
TokenService,
TransactionService,
UserService,
} from '@app/_services';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@ -16,7 +29,7 @@ import {AccountDetails, AreaName, AreaType, Category, Transaction} from '@app/_m
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>;
@ -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,8 +92,10 @@ 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';
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
async (res) => {
if (res !== undefined) { if (res !== undefined) {
this.account = res; this.account = res;
this.cdr.detectChanges(); this.cdr.detectChanges();
@ -102,26 +117,45 @@ export class AccountDetailsComponent implements OnInit {
} else { } else {
alert('Account not found!'); alert('Account not found!');
} }
}); }
);
this.blockSyncService.blockSync(this.accountAddress); this.blockSyncService.blockSync(this.accountAddress);
}); });
this.userService.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,29 +208,38 @@ 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.userService
.resetPin(this.account.vcard.tel[0].value)
.pipe(first())
.subscribe((res) => {
this.loggingService.sendInfoLevelMessage(`Response: ${res}`); this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
}); });
} }
@ -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

@ -10,7 +10,7 @@ import {environment} from '@src/environments/environment';
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,7 +27,7 @@ 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 {
@ -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

@ -11,11 +11,11 @@ const routes: Routes = [
{ 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,7 +1,12 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AccountsComponent } from './accounts.component'; import { AccountsComponent } from './accounts.component';
import {FooterStubComponent, SidebarStubComponent, TopbarStubComponent, UserServiceStub} from '@src/testing'; import {
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
UserServiceStub,
} from '@src/testing';
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 { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
@ -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

@ -14,7 +14,7 @@ import {AccountDetails} from '@app/_models';
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,8 +32,7 @@ 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
@ -42,11 +41,14 @@ export class AccountsComponent implements OnInit {
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

@ -25,13 +25,12 @@ 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

@ -5,7 +5,6 @@ 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;
let fixture: ComponentFixture<CreateAccountComponent>; let fixture: ComponentFixture<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

@ -9,7 +9,7 @@ import {AreaName, Category} from '@app/_models';
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;
@ -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

@ -4,7 +4,12 @@ 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 {
FooterStubComponent,
SidebarStubComponent,
TopbarStubComponent,
UserServiceStub,
} from '@src/testing';
import { AppModule } from '@app/app.module'; import { AppModule } from '@app/app.module';
import { UserService } from '@app/_services'; import { UserService } from '@app/_services';
@ -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

@ -18,8 +18,8 @@ import {Action} from '../../_models';
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

@ -14,7 +14,6 @@ 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: [
@ -29,7 +28,7 @@ import {MatRippleModule} from '@angular/material/core';
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

@ -4,7 +4,7 @@ import {ChangeDetectionStrategy, Component} from '@angular/core';
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/';

View File

@ -11,7 +11,6 @@ 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: [
@ -23,7 +22,7 @@ import {MatCardModule} from '@angular/material/card';
MatFormFieldModule, MatFormFieldModule,
MatSelectModule, MatSelectModule,
MatInputModule, MatInputModule,
MatCardModule MatCardModule,
] ],
}) })
export class PagesModule {} export class PagesModule {}

View File

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

@ -6,30 +6,32 @@ import {CustomErrorStateMatcher} from '@app/_helpers';
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

@ -7,11 +7,11 @@ import {OrganizationComponent} from '@pages/settings/organization/organization.c
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

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

@ -10,7 +10,7 @@ import {exportCsv} from '@app/_helpers';
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

@ -19,7 +19,6 @@ 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],
imports: [ imports: [
@ -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,7 +1,13 @@
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 {
ActivatedRouteStub,
FooterStubComponent,
SidebarStubComponent,
TokenServiceStub,
TopbarStubComponent,
} from '@src/testing';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { TokenService } from '@app/_services'; import { TokenService } from '@app/_services';
import { TokensModule } from '@pages/tokens/tokens.module'; import { TokensModule } from '@pages/tokens/tokens.module';
@ -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

@ -8,23 +8,21 @@ import {Token} from '../../../_models';
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
.getTokenBySymbol(params.get('id'))
.pipe(first())
.subscribe((res) => {
this.token = res; this.token = res;
}); });
}); });
} }
ngOnInit(): void { ngOnInit(): void {}
}
} }

View File

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

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

@ -12,7 +12,7 @@ import {Token} from '../../_models';
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>;

View File

@ -18,7 +18,6 @@ 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: [
@ -37,7 +36,7 @@ import {MatCardModule} from '@angular/material/card';
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

@ -9,7 +9,7 @@ import {strip0x} from '@src/assets/js/ethtx/dist/hex';
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

@ -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,4 +1,10 @@
import {AfterViewInit, ChangeDetectionStrategy, Component, OnInit, ViewChild} from '@angular/core'; import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
OnInit,
ViewChild,
} from '@angular/core';
import { BlockSyncService, TransactionService, UserService } from '@app/_services'; import { BlockSyncService, TransactionService, UserService } from '@app/_services';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator'; import { MatPaginator } from '@angular/material/paginator';
@ -11,7 +17,7 @@ import {Transaction} from '@app/_models';
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

@ -19,12 +19,9 @@ 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: [ imports: [
CommonModule, CommonModule,
TransactionsRoutingModule, TransactionsRoutingModule,
@ -42,6 +39,6 @@ import {MatSnackBarModule} from '@angular/material/snack-bar';
MatCardModule, MatCardModule,
MatRippleModule, MatRippleModule,
MatSnackBarModule, MatSnackBarModule,
] ],
}) })
export class TransactionsModule {} export class TransactionsModule {}

View File

@ -2,7 +2,9 @@ import { MenuSelectionDirective } from '@app/shared/_directives/menu-selection.d
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

@ -2,7 +2,9 @@ import { MenuToggleDirective } from '@app/shared/_directives/menu-toggle.directi
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);

View File

@ -1,14 +1,10 @@
import { Directive, ElementRef, Renderer2 } from '@angular/core'; import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({ @Directive({
selector: '[appMenuToggle]' selector: '[appMenuToggle]',
}) })
export class MenuToggleDirective { export class MenuToggleDirective {
constructor(private elementRef: ElementRef, private renderer: Renderer2) {
constructor(
private elementRef: ElementRef,
private renderer: Renderer2
) {
this.renderer.listen(this.elementRef.nativeElement, 'click', () => { this.renderer.listen(this.elementRef.nativeElement, 'click', () => {
this.onMenuToggle(); this.onMenuToggle();
}); });

View File

@ -2,14 +2,12 @@ import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
@Pipe({ @Pipe({
name: 'safe' name: 'safe',
}) })
export class SafePipe implements PipeTransform { export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {} constructor(private sanitizer: DomSanitizer) {}
transform(url: string, ...args: unknown[]): unknown { transform(url: string, ...args: unknown[]): unknown {
return this.sanitizer.bypassSecurityTrustResourceUrl(url); return this.sanitizer.bypassSecurityTrustResourceUrl(url);
} }
} }

View File

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

View File

@ -5,10 +5,8 @@ import {MAT_DIALOG_DATA} from '@angular/material/dialog';
selector: 'app-error-dialog', selector: 'app-error-dialog',
templateUrl: './error-dialog.component.html', templateUrl: './error-dialog.component.html',
styleUrls: ['./error-dialog.component.scss'], styleUrls: ['./error-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ErrorDialogComponent { export class ErrorDialogComponent {
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {} constructor(@Inject(MAT_DIALOG_DATA) public data: any) {}
} }

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