Refactor mappings to handle object type.

This commit is contained in:
Spencer Ofwiti 2021-06-18 14:03:29 +03:00
parent c4c8a6d13f
commit de83b89580
8 changed files with 1013 additions and 1228 deletions

View File

@ -14,7 +14,7 @@ import { Observable, of, throwError } from 'rxjs';
import { delay, dematerialize, materialize, mergeMap } from 'rxjs/operators';
// Application imports
import { Action, AreaName, AreaType, Category, Token } from '@app/_models';
import { Action } from '@app/_models';
/** A mock of the curated account types. */
const accountTypes: Array<string> = ['user', 'cashier', 'vendor', 'tokenagent', 'group'];
@ -30,10 +30,8 @@ const actions: Array<Action> = [
];
/** A mock of curated area names. */
const areaNames: Array<AreaName> = [
{
name: 'Mukuru Nairobi',
locations: [
const areaNames: object = {
'Mukuru Nairobi': [
'kayaba',
'kayba',
'kambi',
@ -57,10 +55,7 @@ const areaNames: Array<AreaName> = [
'owino road',
'seigei',
],
},
{
name: 'Kinango Kwale',
locations: [
'Kinango Kwale': [
'amani',
'bofu',
'chibuga',
@ -152,10 +147,7 @@ const areaNames: Array<AreaName> = [
'kokotoni',
'mikindani',
],
},
{
name: 'Misc Nairobi',
locations: [
'Misc Nairobi': [
'nairobi',
'west',
'lindi',
@ -222,10 +214,33 @@ const areaNames: Array<AreaName> = [
'mwiki',
'toi market',
],
},
{
name: 'Misc Mombasa',
locations: [
'Kisauni Mombasa': [
'bamburi',
'mnyuchi',
'kisauni',
'kasauni',
'mworoni',
'nyali',
'falcon',
'shanzu',
'bombolulu',
'kandongo',
'kadongo',
'mshomoro',
'mtopanga',
'mjambere',
'majaoni',
'manyani',
'magogoni',
'magongoni',
'junda',
'mwakirunge',
'mshomoroni',
'mjinga',
'mlaleo',
'utange',
],
'Misc Mombasa': [
'mombasa',
'likoni',
'bangla',
@ -245,29 +260,7 @@ const areaNames: Array<AreaName> = [
'tudor',
'diani',
],
},
{
name: 'Kisauni',
locations: [
'bamburi',
'kisauni',
'mworoni',
'nyali',
'shanzu',
'bombolulu',
'mtopanga',
'mjambere',
'majaoni',
'manyani',
'magogoni',
'junda',
'mwakirunge',
'mshomoroni',
],
},
{
name: 'Kilifi',
locations: [
Kilifi: [
'kilfi',
'kilifi',
'mtwapa',
@ -281,18 +274,9 @@ const areaNames: Array<AreaName> = [
'raibai',
'ribe',
],
},
{
name: 'Kakuma',
locations: ['kakuma'],
},
{
name: 'Kitui',
locations: ['kitui', 'mwingi'],
},
{
name: 'Nyanza',
locations: [
Kakuma: ['kakuma'],
Kitui: ['kitui', 'mwingi'],
Nyanza: [
'busia',
'nyalgunga',
'mbita',
@ -308,10 +292,7 @@ const areaNames: Array<AreaName> = [
'migori',
'kusumu',
],
},
{
name: 'Misc Rural Counties',
locations: [
'Misc Rural Counties': [
'makueni',
'meru',
'kisii',
@ -330,42 +311,20 @@ const areaNames: Array<AreaName> = [
'nakuru',
'narok',
],
},
{
name: 'other',
locations: ['other', 'none', 'unknown'],
},
];
other: ['other', 'none', 'unknown'],
};
/** A mock of curated area types. */
const areaTypes: Array<AreaType> = [
{
name: 'urban',
area: ['urban', 'nairobi', 'mombasa'],
},
{
name: 'rural',
area: ['rural', 'kakuma', 'kwale', 'kinango', 'kitui', 'nyanza'],
},
{
name: 'periurban',
area: ['kilifi', 'periurban'],
},
{
name: 'other',
area: ['other'],
},
];
const areaTypes: object = {
urban: ['urban', 'nairobi', 'mombasa', 'kisauni'],
rural: ['rural', 'kakuma', 'kwale', 'kinango', 'kitui', 'nyanza'],
periurban: ['kilifi', 'periurban'],
other: ['other'],
};
/** A mock of the user's business categories */
const categories: Array<Category> = [
{
name: 'system',
products: ['system', 'office main', 'office main phone'],
},
{
name: 'education',
products: [
const categories: object = {
system: ['system', 'office main', 'office main phone'],
education: [
'book',
'coach',
'teacher',
@ -403,7 +362,6 @@ const categories: Array<Category> = [
'demo',
'expert',
'tution',
'tuition',
'children',
'headmaster',
'educator',
@ -422,10 +380,7 @@ const categories: Array<Category> = [
'vitabu',
'kitabu',
],
},
{
name: 'faith',
products: [
faith: [
'pastor',
'imam',
'madrasa',
@ -440,10 +395,7 @@ const categories: Array<Category> = [
'mksiti',
'donor',
],
},
{
name: 'government',
products: [
government: [
'elder',
'chief',
'police',
@ -457,12 +409,8 @@ const categories: Array<Category> = [
'kra',
'mailman',
'immagration',
'immigration',
],
},
{
name: 'environment',
products: [
environment: [
'conservation',
'toilet',
'choo',
@ -486,10 +434,7 @@ const categories: Array<Category> = [
'seedlings',
'recycling',
],
},
{
name: 'farming',
products: [
farming: [
'farm',
'farmer',
'farming',
@ -500,10 +445,7 @@ const categories: Array<Category> = [
'jembe',
'shamba',
],
},
{
name: 'labour',
products: [
labour: [
'artist',
'agent',
'guard',
@ -635,10 +577,7 @@ const categories: Array<Category> = [
'law firm',
'brewer',
],
},
{
name: 'food',
products: [
food: [
'avocado',
'bhajia',
'bajia',
@ -797,14 +736,8 @@ const categories: Array<Category> = [
'barafu',
'juice',
],
},
{
name: 'water',
products: ['maji', 'water'],
},
{
name: 'health',
products: [
water: ['maji', 'water'],
health: [
'agrovet',
'dispensary',
'barakoa',
@ -835,14 +768,8 @@ const categories: Array<Category> = [
'emergency response',
'emergency',
],
},
{
name: 'savings',
products: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'],
},
{
name: 'shop',
products: [
savings: ['chama', 'group', 'savings', 'loan', 'silc', 'vsla', 'credit', 'finance'],
shop: [
'bag',
'bead',
'belt',
@ -914,10 +841,7 @@ const categories: Array<Category> = [
'm-pesa',
'vyombo',
],
},
{
name: 'transport',
products: [
transport: [
'kebeba',
'beba',
'bebabeba',
@ -952,10 +876,7 @@ const categories: Array<Category> = [
'makanga',
'car',
],
},
{
name: 'fuel/energy',
products: [
'fuel/energy': [
'timber',
'timberyard',
'biogas',
@ -984,94 +905,12 @@ const categories: Array<Category> = [
'kerosene',
'diesel',
],
},
{
name: 'other',
products: ['other', 'none', 'unknown', 'none'],
},
];
other: ['other', 'none', 'unknown', 'none'],
};
/** A mock of curated genders */
const genders: Array<string> = ['male', 'female', 'other'];
/** A mock of the tokens in the system. */
const tokens: Array<Token> = [
{
name: 'Giftable Reserve',
symbol: 'GRZ',
address: '0xa686005CE37Dce7738436256982C3903f2E4ea8E',
supply: '1000000001000000000000000000',
decimals: '18',
reserves: {},
},
{
name: 'Demo Token',
symbol: 'DEMO',
address: '0xc80D6aFF8194114c52AEcD84c9f15fd5c8abb187',
supply: '99999999999999998976',
decimals: '18',
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {
weight: '1000000',
balance: '99999999999999998976',
},
},
reserveRatio: '1000000',
owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a',
},
{
name: 'Foo Token',
symbol: 'FOO',
address: '0x9ceD86089f7aBB5A97B40eb0E7521e7aa308d354',
supply: '1000000000000000001014',
decimals: '18',
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E': {
weight: '1000000',
balance: '1000000000000000001014',
},
},
reserveRatio: '1000000',
owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a',
},
{
name: 'testb',
symbol: 'tstb',
address: '0xC63cFA91A3BFf41cE31Ff436f67D3ACBC977DB95',
supply: '99000',
decimals: '18',
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E': { weight: '1000000', balance: '99000' },
},
reserveRatio: '1000000',
owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a',
},
{
name: 'testa',
symbol: 'tsta',
address: '0x8fA4101ef19D0a078239d035659e92b278bD083C',
supply: '9981',
decimals: '18',
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E': { weight: '1000000', balance: '9981' },
},
reserveRatio: '1000000',
owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a',
},
{
name: 'testc',
symbol: 'tstc',
address: '0x4A6fA6bc3BfE4C9661bC692D9798425350C9e3D4',
supply: '100990',
decimals: '18',
reserves: {
'0xa686005CE37Dce7738436256982C3903f2E4ea8E': { weight: '1000000', balance: '100990' },
},
reserveRatio: '1000000',
owner: '0x3Da99AAD2D9CA01D131eFc3B17444b832B31Ff4a',
},
];
/** A mock of curated transaction types. */
const transactionTypes: Array<string> = [
'transactions',
@ -1118,22 +957,12 @@ export class MockBackendInterceptor implements HttpInterceptor {
return approveAction();
case url.endsWith('/areanames') && method === 'GET':
return getAreaNames();
case url.match(/\/areanames\/\w+/) && method === 'GET':
return getAreaNameByLocation();
case url.endsWith('/areatypes') && method === 'GET':
return getAreaTypes();
case url.match(/\/areatypes\/\w+/) && method === 'GET':
return getAreaTypeByArea();
case url.endsWith('/categories') && method === 'GET':
return getCategories();
case url.match(/\/categories\/\w+/) && method === 'GET':
return getCategoryByProduct();
case url.endsWith('/genders') && method === 'GET':
return getGenders();
case url.endsWith('/tokens') && method === 'GET':
return getTokens();
case url.match(/\/tokens\/\w+/) && method === 'GET':
return getTokenBySymbol();
case url.endsWith('/transactiontypes') && method === 'GET':
return getTransactionTypes();
default:
@ -1165,78 +994,21 @@ export class MockBackendInterceptor implements HttpInterceptor {
}
function getAreaNames(): Observable<HttpResponse<any>> {
const areaNameList: Array<string> = areaNames.map((areaName) => areaName.name);
return ok(areaNameList);
}
function getAreaNameByLocation(): Observable<HttpResponse<any>> {
const keywords = stringFromUrl().split(' ');
for (const keyword of keywords) {
const queriedAreaName: AreaName = areaNames.find((areaName) =>
areaName.locations.includes(keyword)
);
if (queriedAreaName) {
return ok(queriedAreaName.name);
}
}
return ok('other');
return ok(areaNames);
}
function getAreaTypes(): Observable<HttpResponse<any>> {
const areaTypeList: Array<string> = areaTypes.map((areaType) => areaType.name);
return ok(areaTypeList);
}
function getAreaTypeByArea(): Observable<HttpResponse<any>> {
const keywords = stringFromUrl().split(' ');
for (const keyword of keywords) {
const queriedAreaType: AreaType = areaTypes.find((areaType) =>
areaType.area.includes(keyword)
);
if (queriedAreaType) {
return ok(queriedAreaType.name);
}
}
return ok('other');
return ok(areaTypes);
}
function getCategories(): Observable<HttpResponse<any>> {
const categoryList: Array<string> = categories.map((category) => category.name);
return ok(categoryList);
}
function getCategoryByProduct(): Observable<HttpResponse<any>> {
const keywords = stringFromUrl().split(' ');
for (const keyword of keywords) {
const queriedCategory: Category = categories.find((category) =>
category.products.includes(keyword)
);
if (queriedCategory) {
return ok(queriedCategory.name);
}
}
return ok('other');
return ok(categories);
}
function getGenders(): Observable<HttpResponse<any>> {
return ok(genders);
}
function getTokens(): Observable<HttpResponse<any>> {
return ok(tokens);
}
function getTokenBySymbol(): Observable<HttpResponse<any>> {
const keywords = stringFromUrl().split(' ');
for (const keyword of keywords) {
const queriedToken: Token = tokens.find((token) => token.symbol === keyword);
if (queriedToken) {
return ok(queriedToken.name);
}
}
return ok('other');
}
function getTransactionTypes(): Observable<HttpResponse<any>> {
return ok(transactionTypes);
}

View File

@ -12,29 +12,5 @@ interface Action {
user: string;
}
/** Area name object interface */
interface AreaName {
/** Locations that map to that area name. */
locations: Array<string>;
/** Name of area */
name: string;
}
/** Area type object interface */
interface AreaType {
/** Areas that map to that area type. */
area: Array<string>;
/** Type of area */
name: string;
}
/** Category object interface */
interface Category {
/** Business category */
name: string;
/** Products that map to that category. */
products: Array<string>;
}
/** @exports */
export { Action, AreaName, AreaType, Category };
export { Action };

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import { environment } from '@src/environments/environment';
import { first } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@ -8,21 +8,51 @@ import { HttpClient } from '@angular/common/http';
providedIn: 'root',
})
export class LocationService {
areaNames: object = {};
private areaNamesList: BehaviorSubject<object> = new BehaviorSubject<object>(this.areaNames);
areaNamesSubject: Observable<object> = this.areaNamesList.asObservable();
areaTypes: object = {};
private areaTypesList: BehaviorSubject<object> = new BehaviorSubject<object>(this.areaTypes);
areaTypesSubject: Observable<object> = this.areaTypesList.asObservable();
constructor(private httpClient: HttpClient) {}
getAreaNames(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/areanames`);
getAreaNames(): void {
this.httpClient
.get(`${environment.cicMetaUrl}/areanames`)
.pipe(first())
.subscribe((res: object) => this.areaNamesList.next(res));
}
getAreaNameByLocation(location: string): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/areanames/${location.toLowerCase()}`);
getAreaNameByLocation(location: string, areaNames: object): string {
const keywords = location.toLowerCase().split(' ');
for (const keyword of keywords) {
const queriedAreaName: string = Object.keys(areaNames).find((key) =>
areaNames[key].includes(keyword)
);
if (queriedAreaName) {
return queriedAreaName;
}
}
}
getAreaTypes(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/areatypes`).pipe(first());
getAreaTypes(): void {
this.httpClient
.get(`${environment.cicMetaUrl}/areatypes`)
.pipe(first())
.subscribe((res: object) => this.areaTypesList.next(res));
}
getAreaTypeByArea(area: string): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/areatypes/${area.toLowerCase()}`);
getAreaTypeByArea(area: string, areaTypes: object): string {
const keywords = area.toLowerCase().split(' ');
for (const keyword of keywords) {
const queriedAreaType: string = Object.keys(areaTypes).find((key) =>
areaTypes[key].includes(keyword)
);
if (queriedAreaType) {
return queriedAreaType;
}
}
}
}

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { CICRegistry } from '@cicnet/cic-client';
import { TokenRegistry } from '@app/_eth';
import { HttpClient } from '@angular/common/http';
import { RegistryService } from '@app/_services/registry.service';
import { Token } from '@app/_models';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
@ -19,7 +18,7 @@ export class TokenService {
tokensSubject: Observable<Array<Token>> = this.tokensList.asObservable();
load: BehaviorSubject<any> = new BehaviorSubject<any>(false);
constructor(private httpClient: HttpClient) {}
constructor() {}
async init(): Promise<void> {
this.registry = await RegistryService.getRegistry();

View File

@ -36,6 +36,10 @@ export class UserService {
private actionsList: BehaviorSubject<any> = new BehaviorSubject<any>(this.actions);
actionsSubject: Observable<Array<any>> = this.actionsList.asObservable();
categories: object = {};
private categoriesList: BehaviorSubject<object> = new BehaviorSubject<object>(this.categories);
categoriesSubject: Observable<object> = this.categoriesList.asObservable();
constructor(
private httpClient: HttpClient,
private loggingService: LoggingService,
@ -244,12 +248,23 @@ export class UserService {
return;
}
getCategories(): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/categories`);
getCategories(): void {
this.httpClient
.get(`${environment.cicMetaUrl}/categories`)
.pipe(first())
.subscribe((res: object) => this.categoriesList.next(res));
}
getCategoryByProduct(product: string): Observable<any> {
return this.httpClient.get(`${environment.cicMetaUrl}/categories/${product.toLowerCase()}`);
getCategoryByProduct(product: string, categories: object): string {
const keywords = product.toLowerCase().split(' ');
for (const keyword of keywords) {
const queriedCategory: string = Object.keys(categories).find((key) =>
categories[key].includes(keyword)
);
if (queriedCategory) {
return queriedCategory;
}
}
}
getAccountTypes(): Observable<any> {

View File

@ -113,24 +113,6 @@
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>Age: </mat-label>
<input
matInput
type="text"
id="age"
placeholder="{{ account?.age }}"
value="{{ account?.age }}"
formControlName="age"
[errorStateMatcher]="matcher"
/>
<mat-error *ngIf="submitted && accountInfoFormStub.age.errors"
>Age is required.</mat-error
>
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label> ACCOUNT TYPE: </mat-label>
@ -150,24 +132,6 @@
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>Bio: </mat-label>
<input
matInput
type="text"
id="bio"
placeholder="{{ account?.products }}"
value="{{ account?.products }}"
formControlName="bio"
[errorStateMatcher]="matcher"
/>
<mat-error *ngIf="submitted && accountInfoFormStub.bio.errors"
>Bio is required.</mat-error
>
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label> GENDER: </mat-label>
@ -187,6 +151,42 @@
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>Age: </mat-label>
<input
matInput
type="text"
id="age"
placeholder="{{ account?.age }}"
value="{{ account?.age }}"
formControlName="age"
[errorStateMatcher]="matcher"
/>
<mat-error *ngIf="submitted && accountInfoFormStub.age.errors"
>Age is required.</mat-error
>
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label>Bio: </mat-label>
<input
matInput
type="text"
id="bio"
placeholder="{{ account?.products }}"
value="{{ account?.products }}"
formControlName="bio"
[errorStateMatcher]="matcher"
/>
<mat-error *ngIf="submitted && accountInfoFormStub.bio.errors"
>Bio is required.</mat-error
>
</mat-form-field>
</div>
<div class="col-md-6 col-lg-4">
<mat-form-field appearance="outline">
<mat-label> BUSINESS CATEGORY: </mat-label>

View File

@ -23,7 +23,7 @@ import { copyToClipboard, CustomErrorStateMatcher, exportCsv } from '@app/_helpe
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 { AccountDetails, Transaction } from '@app/_models';
@Component({
selector: 'app-account-details',
@ -52,9 +52,9 @@ export class AccountDetailsComponent implements OnInit {
accountStatus: any;
accounts: Array<AccountDetails> = [];
accountsType: string = 'all';
categories: Array<Category>;
areaNames: Array<AreaName>;
areaTypes: Array<AreaType>;
categories: Array<string>;
areaNames: Array<string>;
areaTypes: Array<string>;
transaction: any;
transactions: Array<Transaction>;
transactionsType: string = 'all';
@ -115,25 +115,19 @@ export class AccountDetailsComponent implements OnInit {
this.account = res;
this.cdr.detectChanges();
this.loggingService.sendInfoLevelMessage(this.account);
this.locationService
.getAreaNameByLocation(this.account.location.area_name)
.pipe(first())
.subscribe((response) => {
this.area = response;
this.locationService.areaNamesSubject.subscribe((response) => {
this.area = this.locationService.getAreaNameByLocation(
this.account.location.area_name,
response
);
this.cdr.detectChanges();
this.locationService
.getAreaTypeByArea(this.area)
.pipe(first())
.subscribe((result) => {
this.areaType = result;
this.locationService.areaTypesSubject.subscribe((result) => {
this.areaType = this.locationService.getAreaTypeByArea(this.area, result);
this.cdr.detectChanges();
});
});
this.userService
.getCategoryByProduct(this.account.products[0])
.pipe(first())
.subscribe((response) => {
this.category = response;
this.userService.categoriesSubject.subscribe((result) => {
this.category = this.userService.getCategoryByProduct(this.account.products[0], result);
this.cdr.detectChanges();
});
const fullName = this.account.vcard?.fn[0].value.split(' ');
@ -174,18 +168,18 @@ export class AccountDetailsComponent implements OnInit {
this.transactions = transactions;
this.cdr.detectChanges();
});
this.userService
.getCategories()
.pipe(first())
.subscribe((res) => (this.categories = res));
this.locationService
.getAreaNames()
.pipe(first())
.subscribe((res) => (this.areaNames = res));
this.locationService
.getAreaTypes()
.pipe(first())
.subscribe((res) => (this.areaTypes = res));
this.userService.getCategories();
this.userService.categoriesSubject.subscribe((res) => {
this.categories = Object.keys(res);
});
this.locationService.getAreaNames();
this.locationService.areaNamesSubject.subscribe((res) => {
this.areaNames = Object.keys(res);
});
this.locationService.getAreaTypes();
this.locationService.areaTypesSubject.subscribe((res) => {
this.areaTypes = Object.keys(res);
});
this.userService
.getAccountTypes()
.pipe(first())

View File

@ -3,7 +3,6 @@ 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',
@ -15,8 +14,8 @@ export class CreateAccountComponent implements OnInit {
createForm: FormGroup;
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
submitted: boolean = false;
categories: Array<Category>;
areaNames: Array<AreaName>;
categories: Array<string>;
areaNames: Array<string>;
accountTypes: Array<string>;
genders: Array<string>;
@ -40,14 +39,14 @@ export class CreateAccountComponent implements OnInit {
referrer: ['', 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.getCategories();
this.userService.categoriesSubject.subscribe((res) => {
this.categories = Object.keys(res);
});
this.locationService.getAreaNames();
this.locationService.areaNamesSubject.subscribe((res) => {
this.areaNames = Object.keys(res);
});
this.userService
.getAccountTypes()
.pipe(first())