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,
"assets": [
"src/favicon.ico",
"src/assets"
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
@ -68,7 +69,9 @@
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
],
"serviceWorker": true,
"ngswConfigPath": "ngsw-config.json"
},
"dev": {
"fileReplacements": [
@ -110,7 +113,8 @@
"codeCoverage": true,
"assets": [
"src/favicon.ico",
"src/assets"
"src/assets",
"src/manifest.webmanifest"
],
"styles": [
"./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",
"build:dev": "ng build -c dev",
"build:prod": "ng build --prod",
"start:pwa": "npm run build:prod && http-server -p 4200 dist/cic-staff-client",
"test: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",
"e2e": "ng e2e",
"precommit": "format:fix && lint",
"postinstall": "node patch-webpack.js",
"prepare": "husky install",
"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",
"docs": "npm run typedoc && npm run compodoc"
@ -28,6 +34,7 @@
"@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/router": "~10.2.0",
"@angular/service-worker": "~10.2.0",
"@popperjs/core": "^2.5.4",
"angular-datatables": "^9.0.2",
"block-syncer": "^0.2.4",
@ -39,6 +46,7 @@
"datatables.net": "^1.10.22",
"datatables.net-dt": "^1.10.22",
"ethers": "^5.0.31",
"http-server": "^0.12.3",
"jquery": "^3.5.1",
"mocha": "^8.2.1",
"moolb": "^0.1.0",
@ -66,6 +74,7 @@
"@types/node": "^12.20.6",
"codelyzer": "^6.0.0",
"dotenv": "^8.2.0",
"husky": "^6.0.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
@ -74,12 +83,22 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"karma-junit-reporter": "^2.0.1",
"prettier": "^2.3.0",
"pretty-quick": "^3.1.0",
"protractor": "~7.0.0",
"secp256k1": "^4.0.2",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"tslint-angular": "^3.0.3",
"tslint-config-prettier": "^1.18.0",
"tslint-jasmine-rules": "^1.6.1",
"typedoc": "^0.20.36",
"typescript": "~4.0.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.
*/
public async haveAccount(address: string): Promise<boolean> {
return await this.contract.methods.accountIndex(address).call() !== 0;
return (await this.contract.methods.accountIndex(address).call()) !== 0;
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
// 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.
*
* @implements ErrorStateMatcher
*/
export class CustomErrorStateMatcher implements ErrorStateMatcher{
export class CustomErrorStateMatcher implements ErrorStateMatcher {
/**
* Checks whether an invalid input has been made and an error should be made.
*

View File

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

View File

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

View File

@ -71,9 +71,9 @@ export class GlobalErrorHandler extends ErrorHandler {
const isWarning: boolean = this.isWarning(errorTraceString);
if (isWarning) {
this.loggingService.sendWarnLevelMessage(errorTraceString, {error});
this.loggingService.sendWarnLevelMessage(errorTraceString, { error });
} else {
this.loggingService.sendErrorLevelMessage(errorTraceString, this, {error});
this.loggingService.sendErrorLevelMessage(errorTraceString, this, { error });
}
throw error;
@ -110,14 +110,30 @@ export class GlobalErrorHandler extends ErrorHandler {
const route: string = this.router.url;
if (error instanceof HttpErrorResponse) {
this.loggingService.sendErrorLevelMessage(
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`,
this, {error});
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${
(error as HttpErrorResponse).status
}`,
this,
{ error }
);
} else if (error instanceof TypeError) {
this.loggingService.sendErrorLevelMessage(`There was a Type error on route ${route}.\n${error.message}`, this, {error});
this.loggingService.sendErrorLevelMessage(
`There was a Type error on route ${route}.\n${error.message}`,
this,
{ error }
);
} else if (error instanceof Error) {
this.loggingService.sendErrorLevelMessage(`There was a general error on route ${route}.\n${error.message}`, this, {error});
this.loggingService.sendErrorLevelMessage(
`There was a general error on route ${route}.\n${error.message}`,
this,
{ error }
);
} else {
this.loggingService.sendErrorLevelMessage(`Nobody threw an error but something happened on route ${route}!`, this, {error});
this.loggingService.sendErrorLevelMessage(
`Nobody threw an error but something happened on route ${route}!`,
this,
{ error }
);
}
}
}

View File

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

View File

@ -19,7 +19,7 @@ const actions: Array<Action> = [
{ 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: 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. */
@ -112,103 +112,634 @@ const areaTypes: Array<AreaType> = [
const categories: Array<Category> = [
{
name: 'system',
products: ['system', 'office main', 'office main phone']
products: ['system', 'office main', 'office main phone'],
},
{
name: 'education',
products: ['book', 'coach', 'teacher', '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']
products: [
'book',
'coach',
'teacher',
'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',
products: ['pastor', 'imam', 'madrasa', 'religous', 'religious', 'ustadh', 'ustadhi', 'Marital counsellor', 'counsellor', 'church',
'kanisa', 'mksiti', 'donor']
products: [
'pastor',
'imam',
'madrasa',
'religous',
'religious',
'ustadh',
'ustadhi',
'Marital counsellor',
'counsellor',
'church',
'kanisa',
'mksiti',
'donor',
],
},
{
name: 'government',
products: ['elder', 'chief', 'police', 'government', 'country', 'county', 'soldier', 'village admin', 'ward', 'leader', 'kra',
'mailman', 'immagration', 'immigration']
products: [
'elder',
'chief',
'police',
'government',
'country',
'county',
'soldier',
'village admin',
'ward',
'leader',
'kra',
'mailman',
'immagration',
'immigration',
],
},
{
name: 'environment',
products: ['conservation', 'toilet', 'choo', 'garbage', 'fagio', 'waste', 'tree', 'taka', 'scrap', 'cleaning', 'gardener', 'rubbish',
'usafi', 'mazingira', 'miti', 'trash', 'cleaner', 'plastic', 'collection', 'seedling', 'seedlings', 'recycling']
products: [
'conservation',
'toilet',
'choo',
'garbage',
'fagio',
'waste',
'tree',
'taka',
'scrap',
'cleaning',
'gardener',
'rubbish',
'usafi',
'mazingira',
'miti',
'trash',
'cleaner',
'plastic',
'collection',
'seedling',
'seedlings',
'recycling',
],
},
{
name: 'farming',
products: ['farm', 'farmer', 'farming', 'mkulima', 'kulima', 'ukulima', 'wakulima', 'jembe', 'shamba']
products: [
'farm',
'farmer',
'farming',
'mkulima',
'kulima',
'ukulima',
'wakulima',
'jembe',
'shamba',
],
},
{
name: 'labour',
products: ['artist', 'agent', 'guard', 'askari', 'accountant', 'baker', 'beadwork', 'beauty', 'business', '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']
products: [
'artist',
'agent',
'guard',
'askari',
'accountant',
'baker',
'beadwork',
'beauty',
'business',
'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',
products: ['avocado', 'bhajia', 'bajia', 'mbonga', 'bofu', 'beans', 'biscuits', 'biringanya', 'banana', 'bananas', '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']
products: [
'avocado',
'bhajia',
'bajia',
'mbonga',
'bofu',
'beans',
'biscuits',
'biringanya',
'banana',
'bananas',
'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',
products: ['maji', 'water']
products: ['maji', 'water'],
},
{
name: 'health',
products: ['agrovet', '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']
products: [
'agrovet',
'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',
products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance']
products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'],
},
{
name: 'shop',
products: ['bag', 'bead', 'belt', 'bedding', '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']
products: [
'bag',
'bead',
'belt',
'bedding',
'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',
products: ['kebeba', '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']
products: [
'kebeba',
'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',
products: ['timber', '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']
products: [
'timber',
'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',
products: ['other', 'none', 'unknown', 'none']
}
products: ['other', 'none', 'unknown', 'none'],
},
];
/** A mock of curated genders */
@ -336,22 +867,26 @@ export class MockBackendInterceptor implements HttpInterceptor {
}
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);
}
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);
}
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);
}
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);
}
@ -412,5 +947,5 @@ export class MockBackendInterceptor implements HttpInterceptor {
export const MockBackendProvider = {
provide: HTTP_INTERCEPTORS,
useClass: MockBackendInterceptor,
multi: true
multi: true,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import { TestBed } from '@angular/core/testing';
import { UserService } from '@app/_services/user.service';
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
describe('UserService', () => {
let httpClient: HttpClient;
@ -11,7 +11,7 @@ describe('UserService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
imports: [HttpClientTestingModule],
});
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
@ -22,13 +22,33 @@ describe('UserService', () => {
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', () => {
expect(service.getActionById('1')).toEqual({
id: 1,
user: 'Tom',
role: 'enroller',
action: 'Disburse RSV 100',
approval: false
approval: false,
});
});
@ -43,7 +63,7 @@ describe('UserService', () => {
user: 'Tom',
role: 'enroller',
action: 'Disburse RSV 100',
approval: true
approval: true,
});
});
@ -54,7 +74,7 @@ describe('UserService', () => {
user: 'Christine',
role: 'admin',
action: 'Change user phone number',
approval: false
approval: false,
});
});
});

View File

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

View File

@ -1,17 +1,23 @@
import { NgModule } from '@angular/core';
import {Routes, RouterModule, PreloadAllModules} from '@angular/router';
import {AuthGuard} from '@app/_guards';
import { Routes, RouterModule, PreloadAllModules } from '@angular/router';
import { AuthGuard } from '@app/_guards';
const routes: Routes = [
{ path: 'auth', loadChildren: () => import('@app/auth/auth.module').then(m => m.AuthModule) },
{ path: '', loadChildren: () => import('@pages/pages.module').then(m => m.PagesModule), canActivate: [AuthGuard] },
{ path: '**', redirectTo: '', pathMatch: 'full' }
{ path: 'auth', loadChildren: () => import('@app/auth/auth.module').then((m) => m.AuthModule) },
{
path: '',
loadChildren: () => import('@pages/pages.module').then((m) => m.PagesModule),
canActivate: [AuthGuard],
},
{ path: '**', redirectTo: '', pathMatch: 'full' },
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule]
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules,
}),
],
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>

View File

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

View File

@ -1,14 +1,20 @@
import {ChangeDetectionStrategy, Component, HostListener} from '@angular/core';
import {AuthService, ErrorDialogService, LoggingService, TransactionService} from '@app/_services';
import {catchError} from 'rxjs/operators';
import {ChangeDetectionStrategy, Component, HostListener, OnInit} from '@angular/core';
import {
AuthService,
ErrorDialogService,
LoggingService,
TransactionService,
} from '@app/_services';
import { catchError } from 'rxjs/operators';
import { SwUpdate } from '@angular/service-worker';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
export class AppComponent implements OnInit {
title = 'CICADA';
readyStateTarget: number = 3;
readyState: number = 0;
@ -18,7 +24,8 @@ export class AppComponent {
private authService: AuthService,
private transactionService: TransactionService,
private loggingService: LoggingService,
private errorDialogService: ErrorDialogService
private errorDialogService: ErrorDialogService,
private swUpdate: SwUpdate
) {
(async () => {
try {
@ -31,14 +38,26 @@ export class AppComponent {
const publicKeys = await this.authService.getPublicKeys();
await this.authService.mutableKeyStore.importPublicKey(publicKeys);
} 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 🦗?
}
})();
this.mediaQuery.addListener(this.onResize);
this.mediaQuery.addEventListener('change', this.onResize);
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
onResize(e): void {
const sidebar: HTMLElement = document.getElementById('sidebar');

View File

@ -1,27 +1,23 @@
import {BrowserModule} from '@angular/platform-browser';
import {ErrorHandler, NgModule} from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import {AppRoutingModule} from '@app/app-routing.module';
import {AppComponent} from '@app/app.component';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {
GlobalErrorHandler,
MockBackendProvider,
} from '@app/_helpers';
import {DataTablesModule} from 'angular-datatables';
import {SharedModule} from '@app/shared/shared.module';
import {MatTableModule} from '@angular/material/table';
import {AuthGuard} from '@app/_guards';
import {LoggerModule} from 'ngx-logger';
import {environment} from '@src/environments/environment';
import {ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor} from '@app/_interceptors';
import {MutablePgpKeyStore} from '@app/_pgp';
import { AppRoutingModule } from '@app/app-routing.module';
import { AppComponent } from '@app/app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { GlobalErrorHandler, MockBackendProvider } from '@app/_helpers';
import { DataTablesModule } from 'angular-datatables';
import { SharedModule } from '@app/shared/shared.module';
import { MatTableModule } from '@angular/material/table';
import { AuthGuard } from '@app/_guards';
import { LoggerModule } from 'ngx-logger';
import { environment } from '@src/environments/environment';
import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors';
import { MutablePgpKeyStore } from '@app/_pgp';
import { ServiceWorkerModule } from '@angular/service-worker';
@NgModule({
declarations: [
AppComponent
],
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
@ -34,8 +30,9 @@ import {MutablePgpKeyStore} from '@app/_pgp';
level: environment.logLevel,
serverLogLevel: environment.serverLogLevel,
serverLoggingUrl: `${environment.loggingUrl}/api/logs/`,
disableConsoleLogging: false
})
disableConsoleLogging: false,
}),
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
],
providers: [
AuthGuard,
@ -47,6 +44,6 @@ import {MutablePgpKeyStore} from '@app/_pgp';
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, 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 {ElementRef, Renderer2} from '@angular/core';
import { ElementRef, Renderer2 } from '@angular/core';
// tslint:disable-next-line:prefer-const
let elementRef: ElementRef;
// tslint:disable-next-line:prefer-const
let renderer: Renderer2;
describe('PasswordToggleDirective', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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