diff --git a/src/app/_eth/accountIndex.ts b/src/app/_eth/accountIndex.ts index 564479f..bf7445b 100644 --- a/src/app/_eth/accountIndex.ts +++ b/src/app/_eth/accountIndex.ts @@ -1,8 +1,8 @@ -import { environment } from '@src/environments/environment'; import Web3 from 'web3'; +import { Web3Service } from '@app/_services/web3.service'; const abi: Array = require('@src/assets/js/block-sync/data/AccountsIndex.json'); -const web3: Web3 = new Web3(environment.web3Provider); +const web3: Web3 = Web3Service.getInstance(); export class AccountIndex { contractAddress: string; diff --git a/src/app/_eth/token-registry.ts b/src/app/_eth/token-registry.ts index 24cfa00..e1d56fb 100644 --- a/src/app/_eth/token-registry.ts +++ b/src/app/_eth/token-registry.ts @@ -1,8 +1,8 @@ import Web3 from 'web3'; -import { environment } from '@src/environments/environment'; +import { Web3Service } from '@app/_services/web3.service'; const abi: Array = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json'); -const web3: Web3 = new Web3(environment.web3Provider); +const web3: Web3 = Web3Service.getInstance(); export class TokenRegistry { contractAddress: string; diff --git a/src/app/_helpers/mock-backend.ts b/src/app/_helpers/mock-backend.ts index e89d7f2..5e500ba 100644 --- a/src/app/_helpers/mock-backend.ts +++ b/src/app/_helpers/mock-backend.ts @@ -1151,7 +1151,7 @@ export class MockBackendInterceptor implements HttpInterceptor { const queriedCategory: Category = categories.find((category) => category.products.includes(stringFromUrl()) ); - return ok(queriedCategory.name); + return ok(queriedCategory.name || 'other'); } function getAreaNames(): Observable> { @@ -1163,7 +1163,7 @@ export class MockBackendInterceptor implements HttpInterceptor { const queriedAreaName: AreaName = areaNames.find((areaName) => areaName.locations.includes(stringFromUrl()) ); - return ok(queriedAreaName.name); + return ok(queriedAreaName.name || 'other'); } function getAreaTypes(): Observable> { @@ -1175,7 +1175,7 @@ export class MockBackendInterceptor implements HttpInterceptor { const queriedAreaType: AreaType = areaTypes.find((areaType) => areaType.area.includes(stringFromUrl()) ); - return ok(queriedAreaType.name); + return ok(queriedAreaType.name || 'other'); } function getAccountTypes(): Observable> { diff --git a/src/app/_helpers/schema-validation.ts b/src/app/_helpers/schema-validation.ts index f8779a5..371ff44 100644 --- a/src/app/_helpers/schema-validation.ts +++ b/src/app/_helpers/schema-validation.ts @@ -4,7 +4,7 @@ async function personValidation(person: any): Promise { const personValidationErrors: any = await validatePerson(person); if (personValidationErrors) { - personValidationErrors.map((error) => console.error(`${error.message}`)); + personValidationErrors.map((error) => console.error(`${error.message}`, person, error)); } } @@ -12,7 +12,7 @@ async function vcardValidation(vcard: any): Promise { const vcardValidationErrors: any = await validateVcard(vcard); if (vcardValidationErrors) { - vcardValidationErrors.map((error) => console.error(`${error.message}`)); + vcardValidationErrors.map((error) => console.error(`${error.message}`, vcard, error)); } } diff --git a/src/app/_models/token.ts b/src/app/_models/token.ts index 12de380..e3d7136 100644 --- a/src/app/_models/token.ts +++ b/src/app/_models/token.ts @@ -4,7 +4,7 @@ interface Token { address: string; supply: string; decimals: string; - reserves: { + reserves?: { '0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: { weight: string; balance: string; diff --git a/src/app/_services/auth.service.ts b/src/app/_services/auth.service.ts index d6d57c9..4cc952e 100644 --- a/src/app/_services/auth.service.ts +++ b/src/app/_services/auth.service.ts @@ -7,6 +7,8 @@ import { MutableKeyStore, MutablePgpKeyStore } from '@cicnet/crdt-meta'; import { ErrorDialogService } from '@app/_services/error-dialog.service'; import { HttpClient } from '@angular/common/http'; import { HttpError, rejectBody } from '@app/_helpers/global-error-handler'; +import { Staff } from '@app/_models'; +import { BehaviorSubject, Observable } from 'rxjs'; @Injectable({ providedIn: 'root', @@ -14,6 +16,11 @@ import { HttpError, rejectBody } from '@app/_helpers/global-error-handler'; export class AuthService { sessionToken: any; mutableKeyStore: MutableKeyStore; + trustedUsers: Array = []; + private trustedUsersList: BehaviorSubject> = new BehaviorSubject>( + this.trustedUsers + ); + trustedUsersSubject: Observable> = this.trustedUsersList.asObservable(); constructor( private httpClient: HttpClient, @@ -190,10 +197,22 @@ export class AuthService { window.location.reload(); } - getTrustedUsers(): any { - const trustedUsers: Array = []; - this.mutableKeyStore.getPublicKeys().forEach((key) => trustedUsers.push(key.users[0].userId)); - return trustedUsers; + addTrustedUser(user: Staff): void { + const savedIndex = this.trustedUsers.findIndex((staff) => staff.userid === user.userid); + if (savedIndex === 0) { + return; + } + if (savedIndex > 0) { + this.trustedUsers.splice(savedIndex, 1); + } + this.trustedUsers.unshift(user); + this.trustedUsersList.next(this.trustedUsers); + } + + getTrustedUsers(): void { + this.mutableKeyStore.getPublicKeys().forEach((key) => { + this.addTrustedUser(key.users[0].userId); + }); } async getPublicKeys(): Promise { @@ -211,4 +230,8 @@ export class AuthService { getPrivateKey(): any { return this.mutableKeyStore.getPrivateKey(); } + + getPrivateKeyInfo(): any { + return this.getPrivateKey().users[0].userId; + } } diff --git a/src/app/_services/block-sync.service.ts b/src/app/_services/block-sync.service.ts index 9d0c32e..55b03e6 100644 --- a/src/app/_services/block-sync.service.ts +++ b/src/app/_services/block-sync.service.ts @@ -6,6 +6,7 @@ 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 { Web3Service } from '@app/_services/web3.service'; @Injectable({ providedIn: 'root', @@ -16,31 +17,33 @@ export class BlockSyncService { constructor( private transactionService: TransactionService, - private loggingService: LoggingService, - private registryService: RegistryService + private loggingService: LoggingService ) {} - blockSync(address: string = null, offset: number = 0, limit: number = 100): void { + async init(): Promise { + await this.transactionService.init(); + } + + async blockSync(address: string = null, offset: number = 0, limit: number = 100): Promise { this.transactionService.resetTransactionsList(); const settings: Settings = new Settings(this.scan); const readyStateElements: { network: number } = { network: 2 }; settings.w3.provider = environment.web3Provider; - settings.w3.engine = this.registryService.getWeb3(); - settings.registry = this.registryService.getRegistry(); + settings.w3.engine = Web3Service.getInstance(); + settings.registry = await RegistryService.getRegistry(); settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry); settings.txHelper.ontransfer = async (transaction: any): Promise => { - window.dispatchEvent(this.newTransferEvent(transaction)); + window.dispatchEvent(this.newEvent(transaction, 'cic_transfer')); }; settings.txHelper.onconversion = async (transaction: any): Promise => { - window.dispatchEvent(this.newConversionEvent(transaction)); + window.dispatchEvent(this.newEvent(transaction, 'cic_convert')); }; - settings.registry.onload = (addressReturned: number): void => { - this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`); - this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit); - }; - - settings.registry.load(); + // settings.registry.onload = (addressReturned: string): void => { + // this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`); + // this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit); + // }; + this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit); } readyStateProcessor( @@ -78,16 +81,8 @@ export class BlockSyncService { } } - newTransferEvent(tx: any): any { - return new CustomEvent('cic_transfer', { - detail: { - tx, - }, - }); - } - - newConversionEvent(tx: any): any { - return new CustomEvent('cic_convert', { + newEvent(tx: any, eventType: string): any { + return new CustomEvent(eventType, { detail: { tx, }, diff --git a/src/app/_services/index.ts b/src/app/_services/index.ts index 6ded947..88db87d 100644 --- a/src/app/_services/index.ts +++ b/src/app/_services/index.ts @@ -6,3 +6,4 @@ export * from '@app/_services/block-sync.service'; export * from '@app/_services/location.service'; export * from '@app/_services/logging.service'; export * from '@app/_services/error-dialog.service'; +export * from '@app/_services/web3.service'; diff --git a/src/app/_services/registry.service.ts b/src/app/_services/registry.service.ts index 0600f5e..3aa990c 100644 --- a/src/app/_services/registry.service.ts +++ b/src/app/_services/registry.service.ts @@ -1,33 +1,30 @@ 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 { Web3Service } from '@app/_services/web3.service'; @Injectable({ providedIn: 'root', }) export class RegistryService { - web3: Web3 = new Web3(environment.web3Provider); - fileGetter: FileGetter = new HttpGetter(); - registry: CICRegistry = new CICRegistry( - this.web3, - environment.registryAddress, - 'Registry', - this.fileGetter, - ['../../assets/js/block-sync/data'] - ); + static fileGetter: FileGetter = new HttpGetter(); + private static registry: CICRegistry; - constructor() { - this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress); - this.registry.load(); - } + constructor() {} - getRegistry(): any { - return this.registry; - } - - getWeb3(): any { - return this.web3; + public static async getRegistry(): Promise { + if (!RegistryService.registry) { + RegistryService.registry = new CICRegistry( + Web3Service.getInstance(), + environment.registryAddress, + 'Registry', + RegistryService.fileGetter, + ['../../assets/js/block-sync/data'] + ); + RegistryService.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress); + await RegistryService.registry.load(); + } + return RegistryService.registry; } } diff --git a/src/app/_services/token.service.ts b/src/app/_services/token.service.ts index fd7072b..d9ffed8 100644 --- a/src/app/_services/token.service.ts +++ b/src/app/_services/token.service.ts @@ -1,10 +1,10 @@ -import { EventEmitter, Injectable } from '@angular/core'; -import { environment } from '@src/environments/environment'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Injectable } from '@angular/core'; import { CICRegistry } from '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'; @Injectable({ providedIn: 'root', @@ -12,29 +12,65 @@ import { RegistryService } from '@app/_services/registry.service'; export class TokenService { registry: CICRegistry; tokenRegistry: TokenRegistry; - LoadEvent: EventEmitter = new EventEmitter(); + onload: (status: boolean) => void; + tokens: Array = []; + private tokensList: BehaviorSubject> = new BehaviorSubject>(this.tokens); + tokensSubject: Observable> = this.tokensList.asObservable(); - constructor(private httpClient: HttpClient, private registryService: RegistryService) { - this.registry = registryService.getRegistry(); - this.registry.load(); + constructor(private httpClient: HttpClient) {} + + async init(): Promise { + this.registry = await RegistryService.getRegistry(); this.registry.onload = async (address: string): Promise => { this.tokenRegistry = new TokenRegistry( await this.registry.getContractAddressByName('TokenRegistry') ); - this.LoadEvent.next(Date.now()); + this.onload(this.tokenRegistry !== undefined); }; } - async getTokens(): Promise>> { + addToken(token: Token): void { + const savedIndex = this.tokens.findIndex((tk) => tk.address === token.address); + if (savedIndex === 0) { + return; + } + if (savedIndex > 0) { + this.tokens.splice(savedIndex, 1); + } + this.tokens.unshift(token); + this.tokensList.next(this.tokens); + } + + async getTokens(): Promise { const count: number = await this.tokenRegistry.totalTokens(); - return Array.from({ length: count }, async (v, i) => await this.tokenRegistry.entry(i)); + for (let i = 0; i < count; i++) { + const token: Token = await this.getTokenByAddress(await this.tokenRegistry.entry(i)); + this.addToken(token); + } } - getTokenBySymbol(symbol: string): Observable { - return this.httpClient.get(`${environment.cicCacheUrl}/tokens/${symbol}`); + async getTokenByAddress(address: string): Promise { + const token: any = {}; + const tokenContract = await this.registry.addToken(address); + token.address = address; + token.name = await tokenContract.methods.name().call(); + token.symbol = await tokenContract.methods.symbol().call(); + token.supply = await tokenContract.methods.totalSupply().call(); + token.decimals = await tokenContract.methods.decimals().call(); + return token; } - async getTokenBalance(address: string): Promise { + async getTokenBySymbol(symbol: string): Promise> { + const tokenSubject: Subject = new Subject(); + await this.getTokens(); + this.tokensSubject.subscribe((tokens) => { + const queriedToken = tokens.find((token) => token.symbol === symbol); + tokenSubject.next(queriedToken); + }); + return tokenSubject.asObservable(); + } + + async getTokenBalance(address: string): Promise<(address: string) => Promise> { const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0)); return await sarafuToken.methods.balanceOf(address).call(); } diff --git a/src/app/_services/transaction.service.ts b/src/app/_services/transaction.service.ts index ceb0ff1..a26a815 100644 --- a/src/app/_services/transaction.service.ts +++ b/src/app/_services/transaction.service.ts @@ -18,6 +18,7 @@ import { HttpClient } from '@angular/common/http'; import { CICRegistry } from 'cic-client'; import { RegistryService } from '@app/_services/registry.service'; import Web3 from 'web3'; +import { Web3Service } from '@app/_services/web3.service'; const vCard = require('vcard-parser'); @Injectable({ @@ -35,12 +36,15 @@ export class TransactionService { private httpClient: HttpClient, private authService: AuthService, private userService: UserService, - private loggingService: LoggingService, - private registryService: RegistryService + private loggingService: LoggingService ) { - this.web3 = this.registryService.getWeb3(); - this.registry = registryService.getRegistry(); - this.registry.load(); + this.web3 = Web3Service.getInstance(); + } + + async init(): Promise { + await this.authService.init(); + await this.userService.init(); + this.registry = await RegistryService.getRegistry(); } getAllTransactions(offset: number, limit: number): Observable { @@ -48,7 +52,7 @@ export class TransactionService { } getAddressTransactions(address: string, offset: number, limit: number): Observable { - return this.httpClient.get(`${environment.cicCacheUrl}/tx/${address}/${offset}/${limit}`); + return this.httpClient.get(`${environment.cicCacheUrl}/tx/user/${address}/${offset}/${limit}`); } async setTransaction(transaction, cacheSize: number): Promise { @@ -63,10 +67,11 @@ export class TransactionService { .pipe(first()) .subscribe( (res) => { - transaction.sender = this.getAccountInfo(res.body); + transaction.sender = this.getAccountInfo(res, cacheSize); }, (error) => { transaction.sender = defaultAccount; + this.userService.addAccount(defaultAccount, cacheSize); } ); this.userService @@ -74,10 +79,11 @@ export class TransactionService { .pipe(first()) .subscribe( (res) => { - transaction.recipient = this.getAccountInfo(res.body); + transaction.recipient = this.getAccountInfo(res, cacheSize); }, (error) => { transaction.recipient = defaultAccount; + this.userService.addAccount(defaultAccount, cacheSize); } ); } finally { @@ -98,10 +104,11 @@ export class TransactionService { .pipe(first()) .subscribe( (res) => { - conversion.sender = conversion.recipient = this.getAccountInfo(res.body); + conversion.sender = conversion.recipient = this.getAccountInfo(res); }, (error) => { conversion.sender = conversion.recipient = defaultAccount; + this.userService.addAccount(defaultAccount, cacheSize); } ); } finally { @@ -110,9 +117,16 @@ export class TransactionService { } addTransaction(transaction, cacheSize: number): void { + const savedIndex = this.transactions.findIndex((tx) => tx.tx.txHash === transaction.tx.txHash); + if (savedIndex === 0) { + return; + } + if (savedIndex > 0) { + this.transactions.splice(savedIndex, 1); + } this.transactions.unshift(transaction); if (this.transactions.length > cacheSize) { - this.transactions.length = cacheSize; + this.transactions.length = Math.min(this.transactions.length, cacheSize); } this.transactionList.next(this.transactions); } @@ -122,9 +136,10 @@ export class TransactionService { this.transactionList.next(this.transactions); } - getAccountInfo(account: string): any { + getAccountInfo(account: string, cacheSize: number = 100): any { const accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data; accountInfo.vcard = vCard.parse(atob(accountInfo.vcard)); + this.userService.addAccount(accountInfo, cacheSize); return accountInfo; } @@ -134,41 +149,43 @@ export class TransactionService { recipientAddress: string, value: number ): Promise { - 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 data = fromHex(methodSignature + strip0x(abi)); - const tx = new Tx(environment.bloxbergChainId); - tx.nonce = await this.web3.eth.getTransactionCount(senderAddress); - tx.gasPrice = Number(await this.web3.eth.getGasPrice()); - tx.gasLimit = 8000000; - tx.to = fromHex(strip0x(transferAuthAddress)); - tx.value = toValue(value); - tx.data = data; - const txMsg = tx.message(); - const privateKey = this.authService.mutableKeyStore.getPrivateKey(); - if (!privateKey.isDecrypted()) { - const password = window.prompt('password'); - await privateKey.decrypt(password); - } - const signatureObject = secp256k1.ecdsaSign(txMsg, privateKey.keyPacket.privateParams.d); - const r = signatureObject.signature.slice(0, 32); - const s = signatureObject.signature.slice(32); - const v = signatureObject.recid; - tx.setSignature(r, s, v); - const txWire = add0x(toHex(tx.serializeRLP())); - const result = await this.web3.eth.sendSignedTransaction(txWire); - this.loggingService.sendInfoLevelMessage(`Result: ${result}`); - const transaction = await this.web3.eth.getTransaction(result.transactionHash); - this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`); + this.registry.onload = async (addressReturned: string): Promise => { + 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 data = fromHex(methodSignature + strip0x(abi)); + const tx = new Tx(environment.bloxbergChainId); + tx.nonce = await this.web3.eth.getTransactionCount(senderAddress); + tx.gasPrice = Number(await this.web3.eth.getGasPrice()); + tx.gasLimit = 8000000; + tx.to = fromHex(strip0x(transferAuthAddress)); + tx.value = toValue(value); + tx.data = data; + const txMsg = tx.message(); + const privateKey = this.authService.mutableKeyStore.getPrivateKey(); + if (!privateKey.isDecrypted()) { + const password = window.prompt('password'); + await privateKey.decrypt(password); + } + const signatureObject = secp256k1.ecdsaSign(txMsg, privateKey.keyPacket.privateParams.d); + const r = signatureObject.signature.slice(0, 32); + const s = signatureObject.signature.slice(32); + const v = signatureObject.recid; + tx.setSignature(r, s, v); + const txWire = add0x(toHex(tx.serializeRLP())); + const result = await this.web3.eth.sendSignedTransaction(txWire); + this.loggingService.sendInfoLevelMessage(`Result: ${result}`); + const transaction = await this.web3.eth.getTransaction(result.transactionHash); + this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`); + }; } } diff --git a/src/app/_services/user.service.ts b/src/app/_services/user.service.ts index af8ac68..7de59d9 100644 --- a/src/app/_services/user.service.ts +++ b/src/app/_services/user.service.ts @@ -39,20 +39,20 @@ export class UserService { private httpClient: HttpClient, private loggingService: LoggingService, private tokenService: TokenService, - private registryService: RegistryService, private authService: AuthService - ) { - this.authService.init().then(() => { - this.keystore = authService.mutableKeyStore; - this.signer = new PGPSigner(this.keystore); - }); - this.registry = registryService.getRegistry(); - this.registry.load(); + ) {} + + async init(): Promise { + await this.authService.init(); + await this.tokenService.init(); + this.keystore = this.authService.mutableKeyStore; + this.signer = new PGPSigner(this.keystore); + this.registry = await RegistryService.getRegistry(); } resetPin(phone: string): Observable { const params: HttpParams = new HttpParams().set('phoneNumber', phone); - return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params }); + return this.httpClient.put(`${environment.cicUssdUrl}/pin`, { params }); } getAccountStatus(phone: string): Observable { @@ -183,9 +183,7 @@ export class UserService { 'AccountRegistry' ); const accountIndexQuery = new AccountIndex(accountIndexAddress); - const accountAddresses: Array = await accountIndexQuery.last( - await accountIndexQuery.totalAccounts() - ); + const accountAddresses: Array = await accountIndexQuery.last(limit); this.loggingService.sendInfoLevelMessage(accountAddresses); for (const accountAddress of accountAddresses.slice(offset, offset + limit)) { await this.getAccountByAddress(accountAddress, limit); @@ -203,16 +201,14 @@ export class UserService { 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] - ); + this.tokenService.onload = async (status: boolean): Promise => { + 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); + this.addAccount(accountInfo, limit); accountSubject.next(accountInfo); }); return accountSubject.asObservable(); @@ -264,4 +260,23 @@ export class UserService { getGenders(): Observable { return this.httpClient.get(`${environment.cicMetaUrl}/genders`); } + + addAccount(account: AccountDetails, cacheSize: number): void { + const savedIndex = this.accounts.findIndex( + (acc) => + acc.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0] === + account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0] + ); + if (savedIndex === 0) { + return; + } + if (savedIndex > 0) { + this.accounts.splice(savedIndex, 1); + } + this.accounts.unshift(account); + if (this.accounts.length > cacheSize) { + this.accounts.length = Math.min(this.accounts.length, cacheSize); + } + this.accountsList.next(this.accounts); + } } diff --git a/src/app/_services/web3.service.spec.ts b/src/app/_services/web3.service.spec.ts new file mode 100644 index 0000000..0abf88f --- /dev/null +++ b/src/app/_services/web3.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { Web3Service } from './web3.service'; + +describe('Web3Service', () => { + let service: Web3Service; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(Web3Service); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/_services/web3.service.ts b/src/app/_services/web3.service.ts new file mode 100644 index 0000000..b7f5cd4 --- /dev/null +++ b/src/app/_services/web3.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import Web3 from 'web3'; +import { environment } from '@src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class Web3Service { + private static web3: Web3; + + constructor() {} + + public static getInstance(): Web3 { + if (!Web3Service.web3) { + Web3Service.web3 = new Web3(environment.web3Provider); + } + return Web3Service.web3; + } +} diff --git a/src/app/app.component.html b/src/app/app.component.html index 83fd567..f17c3b3 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,2 +1 @@ - diff --git a/src/app/app.component.ts b/src/app/app.component.ts index dfda722..d9e4a2e 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -27,28 +27,23 @@ export class AppComponent implements OnInit { private errorDialogService: ErrorDialogService, private swUpdate: SwUpdate ) { - (async () => { - try { - await this.authService.init(); - // this.authService.getPublicKeys() - // .pipe(catchError(async (error) => { - // this.loggingService.sendErrorLevelMessage('Unable to load trusted public keys.', this, {error}); - // this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'}); - // })).subscribe(this.authService.mutableKeyStore.importPublicKey); - const publicKeys = await this.authService.getPublicKeys(); - await this.authService.mutableKeyStore.importPublicKey(publicKeys); - } catch (error) { - this.errorDialogService.openDialog({ - message: 'Trusted keys endpoint cannot be reached. Please try again later.', - }); - // TODO do something to halt user progress...show a sad cicada page 🦗? - } - })(); this.mediaQuery.addEventListener('change', this.onResize); this.onResize(this.mediaQuery); } - ngOnInit(): void { + async ngOnInit(): Promise { + await this.authService.init(); + await this.transactionService.init(); + try { + const publicKeys = await this.authService.getPublicKeys(); + await this.authService.mutableKeyStore.importPublicKey(publicKeys); + this.authService.getTrustedUsers(); + } catch (error) { + this.errorDialogService.openDialog({ + message: 'Trusted keys endpoint cannot be reached. Please try again later.', + }); + // TODO do something to halt user progress...show a sad cicada page 🦗? + } if (!this.swUpdate.isEnabled) { this.swUpdate.available.subscribe(() => { if (confirm('New Version available. Load New Version?')) { diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index 2355239..501293c 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -1,8 +1,9 @@ +
- +

CICADA

diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index 495ff88..7c6f993 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -10,6 +10,7 @@ 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 { SharedModule } from '@app/shared/shared.module'; @NgModule({ declarations: [AuthComponent, PasswordToggleDirective], @@ -22,6 +23,7 @@ import { MatRippleModule } from '@angular/material/core'; MatInputModule, MatButtonModule, MatRippleModule, + SharedModule, ], }) export class AuthModule {} diff --git a/src/app/pages/accounts/account-details/account-details.component.html b/src/app/pages/accounts/account-details/account-details.component.html index 4cdec33..646dd4a 100644 --- a/src/app/pages/accounts/account-details/account-details.component.html +++ b/src/app/pages/accounts/account-details/account-details.component.html @@ -34,7 +34,7 @@ {{account?.vcard?.fn[0].value}} Balance: {{account?.balance | tokenRatio}} SRF - Created: {{account?.date_registered | date}} + Created: {{account?.date_registered | unixDate}} Address: {{accountAddress}} Copy @@ -48,10 +48,19 @@
- Name(s): * - - Name is required. + First Name: * + + First Name is required. + +
+ +
+ + Last Name(s): * + + Last Name is required.
@@ -210,12 +219,9 @@ {{account?.vcard?.fn[0].value}} {{account?.balance | tokenRatio}} - {{account?.date_registered | date}} + {{account?.date_registered | unixDate}} - - {{accountStatus}} - - + {{accountStatus}} @@ -228,7 +234,7 @@ - +
@@ -252,17 +258,17 @@ search - Sender - {{transaction?.sender?.vcard.fn[0].value}} + {{transaction?.sender?.vcard.fn[0].value || transaction.from}} Recipient - {{transaction?.recipient?.vcard.fn[0].value}} + {{transaction?.recipient?.vcard.fn[0].value || transaction.to}} @@ -275,7 +281,7 @@ Created - {{transaction?.tx.timestamp | date}} + {{transaction?.tx.timestamp | unixDate}} @@ -285,10 +291,10 @@ - - - + + + @@ -337,7 +343,7 @@ CREATED - {{user?.date_registered | date}} + {{user?.date_registered | unixDate}} diff --git a/src/app/pages/accounts/account-details/account-details.component.ts b/src/app/pages/accounts/account-details/account-details.component.ts index 5a26eab..312682a 100644 --- a/src/app/pages/accounts/account-details/account-details.component.ts +++ b/src/app/pages/accounts/account-details/account-details.component.ts @@ -78,8 +78,17 @@ export class AccountDetailsComponent implements OnInit { private cdr: ChangeDetectorRef, private snackBar: MatSnackBar ) { + this.route.paramMap.subscribe((params: Params) => { + this.accountAddress = add0x(params.get('id')); + this.bloxbergLink = + 'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions'; + }); + } + + async ngOnInit(): Promise { this.accountInfoForm = this.formBuilder.group({ - name: ['', Validators.required], + firstName: ['', Validators.required], + lastName: ['', Validators.required], phoneNumber: ['', Validators.required], age: ['', Validators.required], type: ['', Validators.required], @@ -90,36 +99,71 @@ export class AccountDetailsComponent implements OnInit { location: ['', Validators.required], locationType: ['', Validators.required], }); - 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!'); - } + await this.blockSyncService.init(); + await this.tokenService.init(); + await this.transactionService.init(); + await this.userService.init(); + await this.blockSyncService.blockSync(this.accountAddress); + this.userService.resetAccountsList(); + (await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe( + async (res) => { + if (res !== undefined) { + this.account = res; + this.cdr.detectChanges(); + this.loggingService.sendInfoLevelMessage(this.account); + const fullName = this.account.vcard?.fn[0].value.split(' '); + this.accountInfoForm.patchValue({ + firstName: fullName[0], + lastName: fullName.slice(1).join(' '), + 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 || + this.userService.getCategoryByProduct(this.account.products[0]), + userLocation: this.account.location.area_name, + location: + this.account.location.area || + this.locationService + .getAreaNameByLocation(this.account.location.area_name) + .pipe(first()) + .subscribe((response) => { + return response; + }), + locationType: + this.account.location.area_type || + this.locationService + .getAreaTypeByArea(this.accountInfoFormStub.location.value) + .pipe(first()) + .subscribe((response) => { + return response; + }), + }); + this.userService + .getAccountStatus(this.account.vcard?.tel[0].value) + .pipe(first()) + .subscribe((response) => (this.accountStatus = response.status)); + } else { + alert('Account not found!'); } - ); - this.blockSyncService.blockSync(this.accountAddress); + } + ); + this.userService.accountsSubject.subscribe((accounts) => { + this.userDataSource = new MatTableDataSource(accounts); + this.userDataSource.paginator = this.userTablePaginator; + this.userDataSource.sort = this.userTableSort; + this.accounts = accounts; + this.cdr.detectChanges(); + }); + + this.transactionService.transactionsSubject.subscribe((transactions) => { + this.transactionsDataSource = new MatTableDataSource(transactions); + this.transactionsDataSource.paginator = this.transactionTablePaginator; + this.transactionsDataSource.sort = this.transactionTableSort; + this.transactions = transactions; + this.cdr.detectChanges(); }); this.userService .getCategories() @@ -147,22 +191,6 @@ export class AccountDetailsComponent implements OnInit { .subscribe((res) => (this.genders = res)); } - ngOnInit(): void { - this.userService.accountsSubject.subscribe((accounts) => { - this.userDataSource = new MatTableDataSource(accounts); - this.userDataSource.paginator = this.userTablePaginator; - this.userDataSource.sort = this.userTableSort; - this.accounts = accounts; - }); - - this.transactionService.transactionsSubject.subscribe((transactions) => { - this.transactionsDataSource = new MatTableDataSource(transactions); - this.transactionsDataSource.paginator = this.transactionTablePaginator; - this.transactionsDataSource.sort = this.transactionTableSort; - this.transactions = transactions; - }); - } - doTransactionFilter(value: string): void { this.transactionsDataSource.filter = value.trim().toLocaleLowerCase(); } @@ -192,7 +220,7 @@ export class AccountDetailsComponent implements OnInit { } const accountKey = await this.userService.changeAccountInfo( this.accountAddress, - this.accountInfoFormStub.name.value, + this.accountInfoFormStub.firstName.value + ' ' + this.accountInfoFormStub.lastName.value, this.accountInfoFormStub.phoneNumber.value, this.accountInfoFormStub.age.value, this.accountInfoFormStub.type.value, diff --git a/src/app/pages/accounts/account-search/account-search.component.ts b/src/app/pages/accounts/account-search/account-search.component.ts index ef665f8..800b0ce 100644 --- a/src/app/pages/accounts/account-search/account-search.component.ts +++ b/src/app/pages/accounts/account-search/account-search.component.ts @@ -30,7 +30,8 @@ export class AccountSearchComponent implements OnInit { private router: Router ) {} - ngOnInit(): void { + async ngOnInit(): Promise { + await this.userService.init(); this.nameSearchForm = this.formBuilder.group({ name: ['', Validators.required], }); diff --git a/src/app/pages/accounts/accounts.component.html b/src/app/pages/accounts/accounts.component.html index 7252fcf..43628aa 100644 --- a/src/app/pages/accounts/accounts.component.html +++ b/src/app/pages/accounts/accounts.component.html @@ -56,7 +56,7 @@ CREATED - {{user?.date_registered | date}} + {{user?.date_registered | unixDate}} diff --git a/src/app/pages/accounts/accounts.component.ts b/src/app/pages/accounts/accounts.component.ts index 853997a..c009f46 100644 --- a/src/app/pages/accounts/accounts.component.ts +++ b/src/app/pages/accounts/accounts.component.ts @@ -32,28 +32,26 @@ 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 - await this.userService.loadAccounts(100); - } catch (error) { - this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error }); - } - })(); - this.userService - .getAccountTypes() - .pipe(first()) - .subscribe((res) => (this.accountTypes = res)); - } + ) {} - ngOnInit(): void { + async ngOnInit(): Promise { + await this.userService.init(); + try { + // 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.userService.accountsSubject.subscribe((accounts) => { this.dataSource = new MatTableDataSource(accounts); this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; this.accounts = accounts; }); + this.userService + .getAccountTypes() + .pipe(first()) + .subscribe((res) => (this.accountTypes = res)); } doFilter(value: string): void { diff --git a/src/app/pages/accounts/create-account/create-account.component.ts b/src/app/pages/accounts/create-account/create-account.component.ts index 2f196e7..30692ef 100644 --- a/src/app/pages/accounts/create-account/create-account.component.ts +++ b/src/app/pages/accounts/create-account/create-account.component.ts @@ -26,7 +26,8 @@ export class CreateAccountComponent implements OnInit { private userService: UserService ) {} - ngOnInit(): void { + async ngOnInit(): Promise { + await this.userService.init(); this.createForm = this.formBuilder.group({ accountType: ['', Validators.required], idNumber: ['', Validators.required], diff --git a/src/app/pages/admin/admin.component.ts b/src/app/pages/admin/admin.component.ts index cace112..d33be3c 100644 --- a/src/app/pages/admin/admin.component.ts +++ b/src/app/pages/admin/admin.component.ts @@ -30,7 +30,10 @@ 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) {} + + async ngOnInit(): Promise { + await this.userService.init(); this.userService.getActions(); this.userService.actionsSubject.subscribe((actions) => { this.dataSource = new MatTableDataSource(actions); @@ -40,8 +43,6 @@ export class AdminComponent implements OnInit { }); } - ngOnInit(): void {} - doFilter(value: string): void { this.dataSource.filter = value.trim().toLocaleLowerCase(); } diff --git a/src/app/pages/settings/settings.component.html b/src/app/pages/settings/settings.component.html index 9ed5869..ac74e63 100644 --- a/src/app/pages/settings/settings.component.html +++ b/src/app/pages/settings/settings.component.html @@ -23,9 +23,10 @@ SETTINGS
-

Kobo Toolbox Credentials

- Username: admin_reserve
- Password: ******** +

CICADA Admin Credentials

+ UserId: {{ userInfo?.userid }}
+ Username: {{ userInfo?.name }}
+ Email: {{ userInfo?.email }}

diff --git a/src/app/pages/settings/settings.component.ts b/src/app/pages/settings/settings.component.ts index 4806258..8e167b0 100644 --- a/src/app/pages/settings/settings.component.ts +++ b/src/app/pages/settings/settings.component.ts @@ -17,19 +17,22 @@ export class SettingsComponent implements OnInit { dataSource: MatTableDataSource; displayedColumns: Array = ['name', 'email', 'userId']; trustedUsers: Array; + userInfo: Staff; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; constructor(private authService: AuthService) {} - ngOnInit(): void { - const d = new Date(); - this.date = `${d.getDate()}/${d.getMonth()}/${d.getFullYear()}`; - this.trustedUsers = this.authService.getTrustedUsers(); - this.dataSource = new MatTableDataSource(this.trustedUsers); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; + async ngOnInit(): Promise { + await this.authService.init(); + this.authService.trustedUsersSubject.subscribe((users) => { + this.dataSource = new MatTableDataSource(users); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.trustedUsers = users; + }); + this.userInfo = this.authService.getPrivateKeyInfo(); } doFilter(value: string): void { diff --git a/src/app/pages/tokens/token-details/token-details.component.html b/src/app/pages/tokens/token-details/token-details.component.html index 8f95e9d..4d90527 100644 --- a/src/app/pages/tokens/token-details/token-details.component.html +++ b/src/app/pages/tokens/token-details/token-details.component.html @@ -1,60 +1,36 @@ - -
- - - - - - -
- - -
- -
-
- - Token - -
-
- Name: {{token.name}} -
-
- Symbol: {{token.symbol}} -
-
- Address: {{token.address}} -
-
- Details: A community inclusive currency for trading among lower to middle income societies. -
-
- Supply: {{token.supply | tokenRatio}} -

-
-

Reserve

-
- Weight: {{token.reserveRatio}} -
-
- Owner: {{token.owner}} -
-
-
+
+
+ +
+ TOKEN DETAILS + +
+
+
+
+ Name: {{token?.name}} +
+
+ Symbol: {{token?.symbol}} +
+
+ Address: {{token?.address}} +
+
+ Details: A community inclusive currency for trading among lower to middle income societies. +
+
+ Supply: {{token?.supply | tokenRatio}} +

+
+

Reserve

+
+ Weight: {{token?.reserveRatio}} +
+
+ Owner: {{token?.owner}}
-
- - -
- diff --git a/src/app/pages/tokens/token-details/token-details.component.ts b/src/app/pages/tokens/token-details/token-details.component.ts index bb0ece6..86cb491 100644 --- a/src/app/pages/tokens/token-details/token-details.component.ts +++ b/src/app/pages/tokens/token-details/token-details.component.ts @@ -1,8 +1,12 @@ -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, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; +import { Token } from '@app/_models'; @Component({ selector: 'app-token-details', @@ -11,18 +15,16 @@ import { Token } from '../../../_models'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class TokenDetailsComponent implements OnInit { - token: Token; + @Input() token: Token; - 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; - }); - }); - } + @Output() closeWindow: EventEmitter = new EventEmitter(); + + constructor() {} ngOnInit(): void {} + + close(): void { + this.token = null; + this.closeWindow.emit(this.token); + } } diff --git a/src/app/pages/tokens/tokens.component.html b/src/app/pages/tokens/tokens.component.html index 486e39a..5cf5ebe 100644 --- a/src/app/pages/tokens/tokens.component.html +++ b/src/app/pages/tokens/tokens.component.html @@ -24,6 +24,9 @@
+ + + Filter diff --git a/src/app/pages/tokens/tokens.component.ts b/src/app/pages/tokens/tokens.component.ts index 767694a..3c86267 100644 --- a/src/app/pages/tokens/tokens.component.ts +++ b/src/app/pages/tokens/tokens.component.ts @@ -5,8 +5,7 @@ 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 { Token } from '@app/_models'; @Component({ selector: 'app-tokens', @@ -19,7 +18,8 @@ export class TokensComponent implements OnInit { columnsToDisplay: Array = ['name', 'symbol', 'address', 'supply']; @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort) sort: MatSort; - tokens: Array>; + tokens: Array; + token: Token; constructor( private tokenService: TokenService, @@ -28,22 +28,25 @@ export class TokensComponent implements OnInit { ) {} async ngOnInit(): Promise { - this.tokenService.LoadEvent.subscribe(async () => { - this.tokens = await this.tokenService.getTokens(); + await this.tokenService.init(); + this.tokenService.onload = async (status: boolean): Promise => { + await this.tokenService.getTokens(); + }; + this.tokenService.tokensSubject.subscribe((tokens) => { + this.loggingService.sendInfoLevelMessage(tokens); + this.dataSource = new MatTableDataSource(tokens); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.tokens = tokens; }); - this.tokens = await this.tokenService.getTokens(); - this.loggingService.sendInfoLevelMessage(this.tokens); - this.dataSource = new MatTableDataSource(this.tokens); - this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; } doFilter(value: string): void { this.dataSource.filter = value.trim().toLocaleLowerCase(); } - async viewToken(token): Promise { - await this.router.navigateByUrl(`/tokens/${token.symbol}`); + viewToken(token): void { + this.token = token; } downloadCsv(): void { diff --git a/src/app/pages/transactions/transaction-details/transaction-details.component.html b/src/app/pages/transactions/transaction-details/transaction-details.component.html index 4789857..fb1c6b0 100644 --- a/src/app/pages/transactions/transaction-details/transaction-details.component.html +++ b/src/app/pages/transactions/transaction-details/transaction-details.component.html @@ -1,9 +1,9 @@ -
+
TRANSACTION DETAILS - +
@@ -66,7 +66,7 @@ Success: {{transaction.tx.success}}
  • - Timestamp: {{transaction.tx.timestamp | date}} + Timestamp: {{transaction.tx.timestamp | unixDate}}

  • diff --git a/src/app/pages/transactions/transaction-details/transaction-details.component.ts b/src/app/pages/transactions/transaction-details/transaction-details.component.ts index b99ac13..e6efb47 100644 --- a/src/app/pages/transactions/transaction-details/transaction-details.component.ts +++ b/src/app/pages/transactions/transaction-details/transaction-details.component.ts @@ -1,4 +1,11 @@ -import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core'; import { Router } from '@angular/router'; import { TransactionService } from '@app/_services'; import { copyToClipboard } from '@app/_helpers'; @@ -13,6 +20,9 @@ import { strip0x } from '@src/assets/js/ethtx/dist/hex'; }) export class TransactionDetailsComponent implements OnInit { @Input() transaction; + + @Output() closeWindow: EventEmitter = new EventEmitter(); + senderBloxbergLink: string; recipientBloxbergLink: string; traderBloxbergLink: string; @@ -23,7 +33,8 @@ export class TransactionDetailsComponent implements OnInit { private snackBar: MatSnackBar ) {} - ngOnInit(): void { + async ngOnInit(): Promise { + await this.transactionService.init(); if (this.transaction?.type === 'conversion') { this.traderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions'; @@ -61,4 +72,9 @@ export class TransactionDetailsComponent implements OnInit { this.snackBar.open(address + ' copied successfully!', 'Close', { duration: 3000 }); } } + + close(): void { + this.transaction = null; + this.closeWindow.emit(this.transaction); + } } diff --git a/src/app/pages/transactions/transactions.component.html b/src/app/pages/transactions/transactions.component.html index 7491a36..2099255 100644 --- a/src/app/pages/transactions/transactions.component.html +++ b/src/app/pages/transactions/transactions.component.html @@ -22,7 +22,7 @@
    - +
    @@ -48,12 +48,12 @@ Sender - {{transaction?.sender?.vcard.fn[0].value}} + {{transaction?.sender?.vcard.fn[0].value || transaction.from}} Recipient - {{transaction?.recipient?.vcard.fn[0].value}} + {{transaction?.recipient?.vcard.fn[0].value || transaction.to}} @@ -66,7 +66,7 @@ Created - {{transaction?.tx.timestamp | date}} + {{transaction?.tx.timestamp | unixDate}} diff --git a/src/app/pages/transactions/transactions.component.ts b/src/app/pages/transactions/transactions.component.ts index 56e1f8d..b0b3543 100644 --- a/src/app/pages/transactions/transactions.component.ts +++ b/src/app/pages/transactions/transactions.component.ts @@ -36,17 +36,19 @@ export class TransactionsComponent implements OnInit, AfterViewInit { private blockSyncService: BlockSyncService, private transactionService: TransactionService, private userService: UserService - ) { - this.blockSyncService.blockSync(); - } + ) {} - ngOnInit(): void { + async ngOnInit(): Promise { this.transactionService.transactionsSubject.subscribe((transactions) => { this.transactionDataSource = new MatTableDataSource(transactions); this.transactionDataSource.paginator = this.paginator; this.transactionDataSource.sort = this.sort; this.transactions = transactions; }); + await this.blockSyncService.init(); + await this.transactionService.init(); + await this.userService.init(); + await this.blockSyncService.blockSync(); this.userService .getTransactionTypes() .pipe(first()) diff --git a/src/app/shared/_pipes/unix-date.pipe.spec.ts b/src/app/shared/_pipes/unix-date.pipe.spec.ts new file mode 100644 index 0000000..3a2e039 --- /dev/null +++ b/src/app/shared/_pipes/unix-date.pipe.spec.ts @@ -0,0 +1,8 @@ +import { UnixDatePipe } from './unix-date.pipe'; + +describe('UnixDatePipe', () => { + it('create an instance', () => { + const pipe = new UnixDatePipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/shared/_pipes/unix-date.pipe.ts b/src/app/shared/_pipes/unix-date.pipe.ts new file mode 100644 index 0000000..b2bb75e --- /dev/null +++ b/src/app/shared/_pipes/unix-date.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'unixDate', +}) +export class UnixDatePipe implements PipeTransform { + transform(timestamp: number, ...args: unknown[]): any { + return new Date(timestamp * 1000).toLocaleDateString('en-GB'); + } +} diff --git a/src/app/shared/footer/footer.component.html b/src/app/shared/footer/footer.component.html index 75dbf07..fa0028d 100644 --- a/src/app/shared/footer/footer.component.html +++ b/src/app/shared/footer/footer.component.html @@ -1,5 +1,8 @@ diff --git a/src/app/shared/footer/footer.component.ts b/src/app/shared/footer/footer.component.ts index a2971c8..ad2233a 100644 --- a/src/app/shared/footer/footer.component.ts +++ b/src/app/shared/footer/footer.component.ts @@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class FooterComponent implements OnInit { + currentYear = new Date().getFullYear(); constructor() {} ngOnInit(): void {} diff --git a/src/app/shared/network-status/network-status.component.html b/src/app/shared/network-status/network-status.component.html index e0a1f01..1f907f7 100644 --- a/src/app/shared/network-status/network-status.component.html +++ b/src/app/shared/network-status/network-status.component.html @@ -1,4 +1,4 @@ -