Compare commits
40 Commits
spencer/ce
...
spencer/el
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e82930282 | ||
|
|
1dc751f15f | ||
|
|
9363532284 | ||
|
|
3831694d3e | ||
|
|
631c546c7b | ||
|
|
d94f7e8e15 | ||
|
|
701605be31 | ||
|
|
06326aa0bf | ||
|
|
6904127876 | ||
|
|
4fba6a8383 | ||
|
|
81620600e3 | ||
|
|
8de8e6a30b | ||
|
|
384071d3db | ||
|
|
e5d555577e | ||
|
|
cfe558cccf | ||
|
|
228bca564e | ||
|
|
9fed6fcaf3 | ||
|
|
48ef7cd58d | ||
|
|
16af939af7 | ||
|
|
9782c919d5 | ||
|
|
cef7b8dc04 | ||
|
|
3a6d3bb8c2 | ||
|
|
7ddca00871 | ||
|
|
c60d28a053 | ||
|
|
a4c0e26be9 | ||
|
|
5baffa5fef | ||
|
|
4e06b42cc6 | ||
|
|
8ad736de20 | ||
|
|
872bf65786 | ||
|
|
7a2321f444 | ||
|
|
7b83dbecd3 | ||
|
|
39af716b15 | ||
|
|
be3d389078 | ||
|
|
a003bd7124 | ||
|
|
9235c969fa | ||
|
|
ac231dc03e | ||
|
|
8ae6436460 | ||
|
|
405199cfe3 | ||
|
|
08fda5390a | ||
|
|
506be2eb5b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
/dist
|
/dist
|
||||||
/tmp
|
/tmp
|
||||||
/out-tsc
|
/out-tsc
|
||||||
|
/out
|
||||||
# Only exists if Bazel was run
|
# Only exists if Bazel was run
|
||||||
/bazel-out
|
/bazel-out
|
||||||
|
|
||||||
|
|||||||
7860
package-lock.json
generated
7860
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
57
package.json
57
package.json
@@ -1,6 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "cic-staff-client",
|
"name": "cic-staff-client",
|
||||||
"version": "0.0.0",
|
"version": "0.0.1",
|
||||||
|
"author": "Spencer Ofwiti <maxspencer56@gmail.com>",
|
||||||
|
"description": "A fully featured admin client for managing users and transactions in the CIC network.",
|
||||||
|
"main": "dist/main.js",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"config:dev": "ts-node set-env.ts --environment=dev",
|
"config:dev": "ts-node set-env.ts --environment=dev",
|
||||||
"config:prod": "ts-node set-env.ts --environment=prod",
|
"config:prod": "ts-node set-env.ts --environment=prod",
|
||||||
@@ -18,7 +22,17 @@
|
|||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"precommit": "npm run format:fix && npm run lint",
|
"precommit": "npm run format:fix && npm run lint",
|
||||||
"postinstall": "node patch-webpack.js",
|
"postinstall": "node patch-webpack.js",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"electron": "ng build --base-href ./ && electron .",
|
||||||
|
"electron-tsc": "ng build --base-href ./ && tsc --p src-backend --outDir dist && electron .",
|
||||||
|
"electron:build:dev": "ng build -c dev --base-href ./ && tsc --lib ES2018,DOM --target ES5 src-backend/main.ts --outDir dist && electron .",
|
||||||
|
"electron:package": "npx electron-forge import && npm run make",
|
||||||
|
"start": "electron-forge start",
|
||||||
|
"package": "electron-forge package",
|
||||||
|
"make": "electron-forge make",
|
||||||
|
"elec:package": "npm run build:dev && electron-packager . CICADA --out out --overwrite --asar --icon=dist/cic-staff-client/icons/manifest-icon-512.ico --ignore=^e2e$ --ignore=^src$ --ignore=^src-backend$ --ignore=^.editorconfig$ --ignore=^.gitignore$ --ignore=^angular.json$ --ignore=^browserslist$ --ignore=^karma.conf.js$ --ignore=^package-lock.json$ --ignore=^README.md$ --ignore=^tslint --ignore=^tsconfig --all",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"prebuild": "npm run clean"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -38,6 +52,7 @@
|
|||||||
"bootstrap": "^4.5.3",
|
"bootstrap": "^4.5.3",
|
||||||
"cic-client": "0.1.4",
|
"cic-client": "0.1.4",
|
||||||
"cic-client-meta": "0.0.7-alpha.6",
|
"cic-client-meta": "0.0.7-alpha.6",
|
||||||
|
"electron-squirrel-startup": "^1.0.0",
|
||||||
"ethers": "^5.0.31",
|
"ethers": "^5.0.31",
|
||||||
"http-server": "^0.12.3",
|
"http-server": "^0.12.3",
|
||||||
"jquery": "^3.5.1",
|
"jquery": "^3.5.1",
|
||||||
@@ -53,13 +68,21 @@
|
|||||||
"@angular-devkit/build-angular": "~0.1002.0",
|
"@angular-devkit/build-angular": "~0.1002.0",
|
||||||
"@angular/cli": "~10.2.0",
|
"@angular/cli": "~10.2.0",
|
||||||
"@angular/compiler-cli": "~10.2.0",
|
"@angular/compiler-cli": "~10.2.0",
|
||||||
|
"@electron-forge/cli": "^6.0.0-beta.55",
|
||||||
|
"@electron-forge/maker-deb": "^6.0.0-beta.55",
|
||||||
|
"@electron-forge/maker-rpm": "^6.0.0-beta.55",
|
||||||
|
"@electron-forge/maker-squirrel": "^6.0.0-beta.55",
|
||||||
|
"@electron-forge/maker-zip": "^6.0.0-beta.55",
|
||||||
"@types/datatables.net": "^1.10.19",
|
"@types/datatables.net": "^1.10.19",
|
||||||
|
"@types/electron": "^1.6.10",
|
||||||
"@types/jasmine": "~3.5.0",
|
"@types/jasmine": "~3.5.0",
|
||||||
"@types/jasminewd2": "~2.0.3",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"@types/jquery": "^3.5.4",
|
"@types/jquery": "^3.5.4",
|
||||||
"@types/node": "^12.20.6",
|
"@types/node": "^15.6.1",
|
||||||
"codelyzer": "^6.0.0",
|
"codelyzer": "^6.0.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
|
"electron": "^12.0.9",
|
||||||
|
"electron-packager": "^15.2.0",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
"jasmine-core": "~3.6.0",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-spec-reporter": "~5.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
@@ -72,6 +95,7 @@
|
|||||||
"prettier": "^2.3.0",
|
"prettier": "^2.3.0",
|
||||||
"pretty-quick": "^3.1.0",
|
"pretty-quick": "^3.1.0",
|
||||||
"protractor": "~7.0.0",
|
"protractor": "~7.0.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"secp256k1": "^4.0.2",
|
"secp256k1": "^4.0.2",
|
||||||
"ts-node": "~8.3.0",
|
"ts-node": "~8.3.0",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
@@ -85,5 +109,32 @@
|
|||||||
"hooks": {
|
"hooks": {
|
||||||
"pre-commit": "pretty-quick --staged & ng lint"
|
"pre-commit": "pretty-quick --staged & ng lint"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"forge": {
|
||||||
|
"packagerConfig": {},
|
||||||
|
"makers": [
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-squirrel",
|
||||||
|
"config": {
|
||||||
|
"name": "cic_staff_client"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-zip",
|
||||||
|
"platforms": [
|
||||||
|
"darwin"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-deb",
|
||||||
|
"config": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-rpm",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
139
src-backend/main.ts
Normal file
139
src-backend/main.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import { BrowserWindow, Menu, Tray, app, ipcMain } from 'electron';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as url from 'url';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindow;
|
||||||
|
|
||||||
|
async function createWindow(): Promise<void> {
|
||||||
|
// Create the browser window.
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
fullscreen: false,
|
||||||
|
width: 1000,
|
||||||
|
height: 800,
|
||||||
|
icon: path.join(__dirname, '/cic-staff-client/assets/icons/manifest-icon-512.png'),
|
||||||
|
// Allows IPC and other APIs
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
enableRemoteModule: true,
|
||||||
|
nodeIntegrationInWorker: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// load the dist folder from Angular
|
||||||
|
await mainWindow.loadURL(
|
||||||
|
url.format({
|
||||||
|
pathname: path.join(__dirname, '/cic-staff-client/index.html'),
|
||||||
|
protocol: 'file',
|
||||||
|
slashes: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Open the DevTools.
|
||||||
|
// mainWindow.webContents.openDevTools();
|
||||||
|
|
||||||
|
mainWindow.on('closed', () => {
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openModal(): Promise<void> {
|
||||||
|
const modal = new BrowserWindow({
|
||||||
|
parent: mainWindow,
|
||||||
|
modal: true,
|
||||||
|
show: false,
|
||||||
|
});
|
||||||
|
await modal.loadURL('https://dashboard.sarafu.network/');
|
||||||
|
modal.once('ready-to-show', () => {
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRoot(): boolean {
|
||||||
|
return path.parse(process.cwd()).root === process.cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImages(): void {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
fs.readdir('.', { withFileTypes: true }, ((err, files) => {
|
||||||
|
if (!err) {
|
||||||
|
const re = /(?:\.([^.]+))?$/;
|
||||||
|
const images = files.filter(file => file.isFile() && ['jpg', 'png'].includes(re.exec(file.name)[1])).map(file => `file://${cwd}/${file.name}`);
|
||||||
|
mainWindow.webContents.send('getImagesResponse', images);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDirectory(): void {
|
||||||
|
fs.readdir('.', { withFileTypes: true }, (err, files) => {
|
||||||
|
if (!err) {
|
||||||
|
const directories = files.filter(file => file.isDirectory()).map(file => file.name);
|
||||||
|
if (!isRoot()) {
|
||||||
|
directories.unshift('..');
|
||||||
|
}
|
||||||
|
mainWindow.webContents.send('getDirectoryResponse', directories);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
// Some APIs can only be used after this event occurs.
|
||||||
|
app.on('ready', async () => {
|
||||||
|
let tray = null;
|
||||||
|
tray = new Tray(path.join(__dirname, '/cic-staff-client/assets/icons/manifest-icon-512.png'));
|
||||||
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
|
{ label: 'Item1', type: 'radio' },
|
||||||
|
{ label: 'Item2', type: 'radio' },
|
||||||
|
{ label: 'Item3', type: 'radio', checked: true },
|
||||||
|
{ label: 'Exit', type: 'normal', click: () => { app.quit(); } },
|
||||||
|
]);
|
||||||
|
tray.setToolTip('CIC Administration Dashboard.');
|
||||||
|
// Overrides 'right-click' event.
|
||||||
|
tray.setContextMenu(contextMenu);
|
||||||
|
tray.on('click', (event, arg) => {
|
||||||
|
console.log('Systray was left-clicked.', event, arg);
|
||||||
|
});
|
||||||
|
tray.on('double-click', (event, arg) => {
|
||||||
|
console.log('Systray was double-clicked.', event, arg);
|
||||||
|
});
|
||||||
|
await createWindow();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('activate', async () => {
|
||||||
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
// dock icon is clicked and there are no other windows open.
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
await createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
// for applications and their menu bar to stay active until the user quits
|
||||||
|
// explicitly with Cmd + Q.
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('my-custom-signal', (event, arg) => {
|
||||||
|
console.log('Print to the main process terminal (STDOUT) when signal received from renderer process.');
|
||||||
|
console.log(event);
|
||||||
|
console.log(arg);
|
||||||
|
mainWindow.webContents.send('other-custom-signal', 'message from the backend process');
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('navigateDirectory', (event, arg) => {
|
||||||
|
process.chdir(arg);
|
||||||
|
getImages();
|
||||||
|
getDirectory();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('getFiles', (event, arg) => {
|
||||||
|
const files = fs.readdirSync(__dirname);
|
||||||
|
mainWindow.webContents.send('getFilesResponse', files);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('openModal', async (event, arg) => {
|
||||||
|
await openModal();
|
||||||
|
});
|
||||||
9
src-backend/tsconfig.json
Normal file
9
src-backend/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../dist",
|
||||||
|
"module": "commonjs",
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { environment } from '@src/environments/environment';
|
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
|
import { Web3Service } from '@app/_services/web3.service';
|
||||||
|
|
||||||
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountsIndex.json');
|
const abi: Array<any> = require('@src/assets/js/block-sync/data/AccountsIndex.json');
|
||||||
const web3: Web3 = new Web3(environment.web3Provider);
|
const web3: Web3 = Web3Service.getInstance();
|
||||||
|
|
||||||
export class AccountIndex {
|
export class AccountIndex {
|
||||||
contractAddress: string;
|
contractAddress: string;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
import { environment } from '@src/environments/environment';
|
import { Web3Service } from '@app/_services/web3.service';
|
||||||
|
|
||||||
const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json');
|
const abi: Array<any> = require('@src/assets/js/block-sync/data/TokenUniqueSymbolIndex.json');
|
||||||
const web3: Web3 = new Web3(environment.web3Provider);
|
const web3: Web3 = Web3Service.getInstance();
|
||||||
|
|
||||||
export class TokenRegistry {
|
export class TokenRegistry {
|
||||||
contractAddress: string;
|
contractAddress: string;
|
||||||
|
|||||||
@@ -1151,7 +1151,7 @@ export class MockBackendInterceptor implements HttpInterceptor {
|
|||||||
const queriedCategory: Category = categories.find((category) =>
|
const queriedCategory: Category = categories.find((category) =>
|
||||||
category.products.includes(stringFromUrl())
|
category.products.includes(stringFromUrl())
|
||||||
);
|
);
|
||||||
return ok(queriedCategory.name);
|
return ok(queriedCategory.name || 'other');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAreaNames(): Observable<HttpResponse<any>> {
|
function getAreaNames(): Observable<HttpResponse<any>> {
|
||||||
@@ -1163,7 +1163,7 @@ export class MockBackendInterceptor implements HttpInterceptor {
|
|||||||
const queriedAreaName: AreaName = areaNames.find((areaName) =>
|
const queriedAreaName: AreaName = areaNames.find((areaName) =>
|
||||||
areaName.locations.includes(stringFromUrl())
|
areaName.locations.includes(stringFromUrl())
|
||||||
);
|
);
|
||||||
return ok(queriedAreaName.name);
|
return ok(queriedAreaName.name || 'other');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAreaTypes(): Observable<HttpResponse<any>> {
|
function getAreaTypes(): Observable<HttpResponse<any>> {
|
||||||
@@ -1175,7 +1175,7 @@ export class MockBackendInterceptor implements HttpInterceptor {
|
|||||||
const queriedAreaType: AreaType = areaTypes.find((areaType) =>
|
const queriedAreaType: AreaType = areaTypes.find((areaType) =>
|
||||||
areaType.area.includes(stringFromUrl())
|
areaType.area.includes(stringFromUrl())
|
||||||
);
|
);
|
||||||
return ok(queriedAreaType.name);
|
return ok(queriedAreaType.name || 'other');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAccountTypes(): Observable<HttpResponse<any>> {
|
function getAccountTypes(): Observable<HttpResponse<any>> {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ async function personValidation(person: any): Promise<void> {
|
|||||||
const personValidationErrors: any = await validatePerson(person);
|
const personValidationErrors: any = await validatePerson(person);
|
||||||
|
|
||||||
if (personValidationErrors) {
|
if (personValidationErrors) {
|
||||||
personValidationErrors.map((error) => console.error(`${error.message}`));
|
personValidationErrors.map((error) => console.error(`${error.message}`, person, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ async function vcardValidation(vcard: any): Promise<void> {
|
|||||||
const vcardValidationErrors: any = await validateVcard(vcard);
|
const vcardValidationErrors: any = await validateVcard(vcard);
|
||||||
|
|
||||||
if (vcardValidationErrors) {
|
if (vcardValidationErrors) {
|
||||||
vcardValidationErrors.map((error) => console.error(`${error.message}`));
|
vcardValidationErrors.map((error) => console.error(`${error.message}`, vcard, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ interface Token {
|
|||||||
address: string;
|
address: string;
|
||||||
supply: string;
|
supply: string;
|
||||||
decimals: string;
|
decimals: string;
|
||||||
reserves: {
|
reserves?: {
|
||||||
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
|
'0xa686005CE37Dce7738436256982C3903f2E4ea8E'?: {
|
||||||
weight: string;
|
weight: string;
|
||||||
balance: string;
|
balance: string;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const keyring = new openpgp.Keyring();
|
|||||||
interface MutableKeyStore extends KeyStore {
|
interface MutableKeyStore extends KeyStore {
|
||||||
loadKeyring(): void;
|
loadKeyring(): void;
|
||||||
importKeyPair(publicKey: any, privateKey: any): Promise<void>;
|
importKeyPair(publicKey: any, privateKey: any): Promise<void>;
|
||||||
importPublicKey(publicKey: any): void;
|
importPublicKey(publicKey: any): Promise<void>;
|
||||||
importPrivateKey(privateKey: any): Promise<void>;
|
importPrivateKey(privateKey: any): Promise<void>;
|
||||||
getPublicKeys(): Array<any>;
|
getPublicKeys(): Array<any>;
|
||||||
getTrustedKeys(): Array<any>;
|
getTrustedKeys(): Array<any>;
|
||||||
@@ -28,7 +28,7 @@ interface MutableKeyStore extends KeyStore {
|
|||||||
removePublicKeyForId(keyId: string): any;
|
removePublicKeyForId(keyId: string): any;
|
||||||
removePublicKey(publicKey: any): any;
|
removePublicKey(publicKey: any): any;
|
||||||
clearKeysInKeyring(): void;
|
clearKeysInKeyring(): void;
|
||||||
sign(plainText: string, passphrase: string): Promise<any>;
|
sign(plainText: string): Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MutablePgpKeyStore implements MutableKeyStore {
|
class MutablePgpKeyStore implements MutableKeyStore {
|
||||||
@@ -42,8 +42,8 @@ class MutablePgpKeyStore implements MutableKeyStore {
|
|||||||
await keyring.privateKeys.importKey(privateKey);
|
await keyring.privateKeys.importKey(privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
importPublicKey(publicKey: any): void {
|
async importPublicKey(publicKey: any): Promise<void> {
|
||||||
keyring.publicKeys.importKey(publicKey);
|
await keyring.publicKeys.importKey(publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async importPrivateKey(privateKey: any): Promise<void> {
|
async importPrivateKey(privateKey: any): Promise<void> {
|
||||||
@@ -150,10 +150,11 @@ class MutablePgpKeyStore implements MutableKeyStore {
|
|||||||
keyring.clear();
|
keyring.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async sign(plainText: string, passphrase: string): Promise<any> {
|
async sign(plainText): Promise<any> {
|
||||||
const privateKey = this.getPrivateKey();
|
const privateKey = this.getPrivateKey();
|
||||||
if (!privateKey.isDecrypted()) {
|
if (!privateKey.isDecrypted()) {
|
||||||
await privateKey.decrypt(passphrase);
|
const password = window.prompt('password');
|
||||||
|
await privateKey.decrypt(password);
|
||||||
}
|
}
|
||||||
const opts = {
|
const opts = {
|
||||||
message: openpgp.message.fromText(plainText),
|
message: openpgp.message.fromText(plainText),
|
||||||
|
|||||||
@@ -7,14 +7,20 @@ import { MutableKeyStore, MutablePgpKeyStore } from '@app/_pgp';
|
|||||||
import { ErrorDialogService } from '@app/_services/error-dialog.service';
|
import { ErrorDialogService } from '@app/_services/error-dialog.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
|
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
|
||||||
|
import { Staff } from '@app/_models';
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
sessionToken: any;
|
sessionToken: any;
|
||||||
privateKey: any;
|
|
||||||
mutableKeyStore: MutableKeyStore;
|
mutableKeyStore: MutableKeyStore;
|
||||||
|
trustedUsers: Array<Staff> = [];
|
||||||
|
private trustedUsersList: BehaviorSubject<Array<Staff>> = new BehaviorSubject<Array<Staff>>(
|
||||||
|
this.trustedUsers
|
||||||
|
);
|
||||||
|
trustedUsersSubject: Observable<Array<Staff>> = this.trustedUsersList.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
@@ -31,7 +37,6 @@ export class AuthService {
|
|||||||
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
||||||
}
|
}
|
||||||
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
if (localStorage.getItem(btoa('CICADA_PRIVATE_KEY'))) {
|
||||||
this.privateKey = localStorage.getItem(btoa('CICADA_PRIVATE_KEY'));
|
|
||||||
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
|
await this.mutableKeyStore.importPrivateKey(localStorage.getItem(btoa('CICADA_PRIVATE_KEY')));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,32 +101,21 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async passwordLogin(password: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const o = await this.getChallenge();
|
|
||||||
await this.loginResponse(o, password);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
this.loggingService.sendErrorLevelMessage(
|
|
||||||
`Login challenge failed: Error ${error.status} - ${error.statusText}`,
|
|
||||||
this,
|
|
||||||
{ error }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async login(): Promise<boolean> {
|
async login(): Promise<boolean> {
|
||||||
if (this.sessionToken !== undefined) {
|
if (this.sessionToken !== undefined) {
|
||||||
try {
|
try {
|
||||||
this.getWithToken();
|
const response: boolean = await this.getWithToken();
|
||||||
return true;
|
return response === true;
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
this.loggingService.sendErrorLevelMessage(
|
this.loggingService.sendErrorLevelMessage('Login token failed', this, { error: e });
|
||||||
`Login token failed: Error ${error.status} - ${error.statusText}`,
|
}
|
||||||
this,
|
} else {
|
||||||
{ error }
|
try {
|
||||||
);
|
const o = await this.getChallenge();
|
||||||
|
const response: boolean = await this.loginResponse(o);
|
||||||
|
return response === true;
|
||||||
|
} catch (e) {
|
||||||
|
this.loggingService.sendErrorLevelMessage('Login challenge failed', this, { error: e });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -162,18 +156,23 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loginView(): void {
|
||||||
|
document.getElementById('one').style.display = 'none';
|
||||||
|
document.getElementById('two').style.display = 'block';
|
||||||
|
this.setState('Click button to log in with PGP key ' + this.mutableKeyStore.getPrivateKeyId());
|
||||||
|
}
|
||||||
|
|
||||||
async setKey(privateKeyArmored): Promise<boolean> {
|
async setKey(privateKeyArmored): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
||||||
if (!isValidKeyCheck) {
|
if (!isValidKeyCheck) {
|
||||||
throw Error('The private key is invalid');
|
throw Error('The private key is invalid');
|
||||||
}
|
}
|
||||||
const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(
|
// TODO leaving this out for now.
|
||||||
privateKeyArmored
|
// const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored);
|
||||||
);
|
// if (!isEncryptedKeyCheck) {
|
||||||
if (!isEncryptedKeyCheck) {
|
// throw Error('The private key doesn\'t have a password!');
|
||||||
throw Error('The private key does not have a password!');
|
// }
|
||||||
}
|
|
||||||
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
||||||
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -187,6 +186,7 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
this.loginView();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +197,22 @@ export class AuthService {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrustedUsers(): any {
|
addTrustedUser(user: Staff): void {
|
||||||
const trustedUsers: Array<any> = [];
|
const savedIndex = this.trustedUsers.findIndex((staff) => staff.userid === user.userid);
|
||||||
this.mutableKeyStore.getPublicKeys().forEach((key) => trustedUsers.push(key.users[0].userId));
|
if (savedIndex === 0) {
|
||||||
return trustedUsers;
|
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<any> {
|
async getPublicKeys(): Promise<any> {
|
||||||
@@ -218,4 +230,8 @@ export class AuthService {
|
|||||||
getPrivateKey(): any {
|
getPrivateKey(): any {
|
||||||
return this.mutableKeyStore.getPrivateKey();
|
return this.mutableKeyStore.getPrivateKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPrivateKeyInfo(): any {
|
||||||
|
return this.getPrivateKey().users[0].userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { TransactionService } from '@app/_services/transaction.service';
|
|||||||
import { environment } from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import { LoggingService } from '@app/_services/logging.service';
|
import { LoggingService } from '@app/_services/logging.service';
|
||||||
import { RegistryService } from '@app/_services/registry.service';
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
|
import { Web3Service } from '@app/_services/web3.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -16,31 +17,33 @@ export class BlockSyncService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService
|
||||||
private registryService: RegistryService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
blockSync(address: string = null, offset: number = 0, limit: number = 100): void {
|
async init(): Promise<void> {
|
||||||
|
await this.transactionService.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async blockSync(address: string = null, offset: number = 0, limit: number = 100): Promise<void> {
|
||||||
this.transactionService.resetTransactionsList();
|
this.transactionService.resetTransactionsList();
|
||||||
const settings: Settings = new Settings(this.scan);
|
const settings: Settings = new Settings(this.scan);
|
||||||
const readyStateElements: { network: number } = { network: 2 };
|
const readyStateElements: { network: number } = { network: 2 };
|
||||||
settings.w3.provider = environment.web3Provider;
|
settings.w3.provider = environment.web3Provider;
|
||||||
settings.w3.engine = this.registryService.getWeb3();
|
settings.w3.engine = Web3Service.getInstance();
|
||||||
settings.registry = this.registryService.getRegistry();
|
settings.registry = await RegistryService.getRegistry();
|
||||||
settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry);
|
settings.txHelper = new TransactionHelper(settings.w3.engine, settings.registry);
|
||||||
|
|
||||||
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
|
settings.txHelper.ontransfer = async (transaction: any): Promise<void> => {
|
||||||
window.dispatchEvent(this.newTransferEvent(transaction));
|
window.dispatchEvent(this.newEvent(transaction, 'cic_transfer'));
|
||||||
};
|
};
|
||||||
settings.txHelper.onconversion = async (transaction: any): Promise<any> => {
|
settings.txHelper.onconversion = async (transaction: any): Promise<any> => {
|
||||||
window.dispatchEvent(this.newConversionEvent(transaction));
|
window.dispatchEvent(this.newEvent(transaction, 'cic_convert'));
|
||||||
};
|
};
|
||||||
settings.registry.onload = (addressReturned: number): void => {
|
// settings.registry.onload = (addressReturned: string): void => {
|
||||||
this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
|
// this.loggingService.sendInfoLevelMessage(`Loaded network contracts ${addressReturned}`);
|
||||||
this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
|
// this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
|
||||||
};
|
// };
|
||||||
|
this.readyStateProcessor(settings, readyStateElements.network, address, offset, limit);
|
||||||
settings.registry.load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readyStateProcessor(
|
readyStateProcessor(
|
||||||
@@ -78,16 +81,8 @@ export class BlockSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newTransferEvent(tx: any): any {
|
newEvent(tx: any, eventType: string): any {
|
||||||
return new CustomEvent('cic_transfer', {
|
return new CustomEvent(eventType, {
|
||||||
detail: {
|
|
||||||
tx,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
newConversionEvent(tx: any): any {
|
|
||||||
return new CustomEvent('cic_convert', {
|
|
||||||
detail: {
|
detail: {
|
||||||
tx,
|
tx,
|
||||||
},
|
},
|
||||||
|
|||||||
16
src/app/_services/electron.service.spec.ts
Normal file
16
src/app/_services/electron.service.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ElectronService } from './electron.service';
|
||||||
|
|
||||||
|
describe('ElectronService', () => {
|
||||||
|
let service: ElectronService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(ElectronService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
65
src/app/_services/electron.service.ts
Normal file
65
src/app/_services/electron.service.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { IpcRenderer } from 'electron';
|
||||||
|
import {BehaviorSubject} from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class ElectronService {
|
||||||
|
private ipc: IpcRenderer;
|
||||||
|
images = new BehaviorSubject<string[]>([]);
|
||||||
|
directory = new BehaviorSubject<string[]>([]);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if ((window as any).require) {
|
||||||
|
console.log('Pass');
|
||||||
|
try {
|
||||||
|
console.log('supported');
|
||||||
|
this.ipc = (window as any).require('electron').ipcRenderer;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Could not load electron ipc');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testIpc(): void {
|
||||||
|
this.ipc.on('my-custom-signal', (event, arg) => {
|
||||||
|
console.log('Received acknowledged from backend about receipt of our signal.');
|
||||||
|
console.log(event);
|
||||||
|
console.log(arg);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ipc.on('getImageResponse', (event, images) => {
|
||||||
|
this.images.next(images);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ipc.on('getDirectoryResponse', (event, directory) => {
|
||||||
|
this.directory.next(directory);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Sending message to backend.');
|
||||||
|
this.ipc.send('my-custom-signal', 'hello, are you there?');
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateDirectory(path): any {
|
||||||
|
this.ipc.send('navigateDirectory', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFiles(): Promise<string[]> {
|
||||||
|
console.log('Starting');
|
||||||
|
return new Promise<string[]>((resolve, reject) => {
|
||||||
|
this.ipc.once('getFilesResponse', (event, arg) => {
|
||||||
|
resolve(arg);
|
||||||
|
});
|
||||||
|
this.ipc.send('getFiles');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openModal(): void {
|
||||||
|
console.log('Open a modal.');
|
||||||
|
this.ipc.send('openModal');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,3 +6,4 @@ export * from '@app/_services/block-sync.service';
|
|||||||
export * from '@app/_services/location.service';
|
export * from '@app/_services/location.service';
|
||||||
export * from '@app/_services/logging.service';
|
export * from '@app/_services/logging.service';
|
||||||
export * from '@app/_services/error-dialog.service';
|
export * from '@app/_services/error-dialog.service';
|
||||||
|
export * from '@app/_services/web3.service';
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import Web3 from 'web3';
|
|
||||||
import { environment } from '@src/environments/environment';
|
import { environment } from '@src/environments/environment';
|
||||||
import { CICRegistry, FileGetter } from 'cic-client';
|
import { CICRegistry, FileGetter } from 'cic-client';
|
||||||
import { HttpGetter } from '@app/_helpers';
|
import { HttpGetter } from '@app/_helpers';
|
||||||
|
import { Web3Service } from '@app/_services/web3.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class RegistryService {
|
export class RegistryService {
|
||||||
web3: Web3 = new Web3(environment.web3Provider);
|
static fileGetter: FileGetter = new HttpGetter();
|
||||||
fileGetter: FileGetter = new HttpGetter();
|
private static registry: CICRegistry;
|
||||||
registry: CICRegistry = new CICRegistry(
|
|
||||||
this.web3,
|
|
||||||
environment.registryAddress,
|
|
||||||
'Registry',
|
|
||||||
this.fileGetter,
|
|
||||||
['../../assets/js/block-sync/data']
|
|
||||||
);
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {}
|
||||||
this.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress);
|
|
||||||
this.registry.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
getRegistry(): any {
|
public static async getRegistry(): Promise<CICRegistry> {
|
||||||
return this.registry;
|
if (!RegistryService.registry) {
|
||||||
}
|
RegistryService.registry = new CICRegistry(
|
||||||
|
Web3Service.getInstance(),
|
||||||
getWeb3(): any {
|
environment.registryAddress,
|
||||||
return this.web3;
|
'Registry',
|
||||||
|
RegistryService.fileGetter,
|
||||||
|
['../../assets/js/block-sync/data']
|
||||||
|
);
|
||||||
|
RegistryService.registry.declaratorHelper.addTrust(environment.trustedDeclaratorAddress);
|
||||||
|
await RegistryService.registry.load();
|
||||||
|
}
|
||||||
|
return RegistryService.registry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { EventEmitter, Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '@src/environments/environment';
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
|
||||||
import { CICRegistry } from 'cic-client';
|
import { CICRegistry } from 'cic-client';
|
||||||
import { TokenRegistry } from '@app/_eth';
|
import { TokenRegistry } from '@app/_eth';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { RegistryService } from '@app/_services/registry.service';
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
|
import { Token } from '@app/_models';
|
||||||
|
import {BehaviorSubject, Observable, Subject} from 'rxjs';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -12,29 +12,65 @@ import { RegistryService } from '@app/_services/registry.service';
|
|||||||
export class TokenService {
|
export class TokenService {
|
||||||
registry: CICRegistry;
|
registry: CICRegistry;
|
||||||
tokenRegistry: TokenRegistry;
|
tokenRegistry: TokenRegistry;
|
||||||
LoadEvent: EventEmitter<number> = new EventEmitter<number>();
|
onload: (status: boolean) => void;
|
||||||
|
tokens: Array<Token> = [];
|
||||||
|
private tokensList: BehaviorSubject<Array<Token>> = new BehaviorSubject<Array<Token>>(this.tokens);
|
||||||
|
tokensSubject: Observable<Array<Token>> = this.tokensList.asObservable();
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient, private registryService: RegistryService) {
|
constructor(private httpClient: HttpClient) {}
|
||||||
this.registry = registryService.getRegistry();
|
|
||||||
this.registry.load();
|
async init(): Promise<void> {
|
||||||
|
this.registry = await RegistryService.getRegistry();
|
||||||
this.registry.onload = async (address: string): Promise<void> => {
|
this.registry.onload = async (address: string): Promise<void> => {
|
||||||
this.tokenRegistry = new TokenRegistry(
|
this.tokenRegistry = new TokenRegistry(
|
||||||
await this.registry.getContractAddressByName('TokenRegistry')
|
await this.registry.getContractAddressByName('TokenRegistry')
|
||||||
);
|
);
|
||||||
this.LoadEvent.next(Date.now());
|
this.onload(this.tokenRegistry !== undefined);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTokens(): Promise<Array<Promise<string>>> {
|
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<void> {
|
||||||
const count: number = await this.tokenRegistry.totalTokens();
|
const count: number = await this.tokenRegistry.totalTokens();
|
||||||
return Array.from({ length: count }, async (v, i) => await this.tokenRegistry.entry(i));
|
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<any> {
|
async getTokenByAddress(address: string): Promise<Token> {
|
||||||
return this.httpClient.get(`${environment.cicCacheUrl}/tokens/${symbol}`);
|
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<number> {
|
async getTokenBySymbol(symbol: string): Promise<Observable<Token>> {
|
||||||
|
const tokenSubject: Subject<Token> = new Subject<Token>();
|
||||||
|
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<number>> {
|
||||||
const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0));
|
const sarafuToken = await this.registry.addToken(await this.tokenRegistry.entry(0));
|
||||||
return await sarafuToken.methods.balanceOf(address).call();
|
return await sarafuToken.methods.balanceOf(address).call();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { CICRegistry } from 'cic-client';
|
import { CICRegistry } from 'cic-client';
|
||||||
import { RegistryService } from '@app/_services/registry.service';
|
import { RegistryService } from '@app/_services/registry.service';
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
|
import { Web3Service } from '@app/_services/web3.service';
|
||||||
const vCard = require('vcard-parser');
|
const vCard = require('vcard-parser');
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -34,12 +35,15 @@ export class TransactionService {
|
|||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService
|
||||||
private registryService: RegistryService
|
|
||||||
) {
|
) {
|
||||||
this.web3 = this.registryService.getWeb3();
|
this.web3 = Web3Service.getInstance();
|
||||||
this.registry = registryService.getRegistry();
|
}
|
||||||
this.registry.load();
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
await this.authService.init();
|
||||||
|
await this.userService.init();
|
||||||
|
this.registry = await RegistryService.getRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllTransactions(offset: number, limit: number): Observable<any> {
|
getAllTransactions(offset: number, limit: number): Observable<any> {
|
||||||
@@ -47,7 +51,7 @@ export class TransactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAddressTransactions(address: string, offset: number, limit: number): Observable<any> {
|
getAddressTransactions(address: string, offset: number, limit: number): Observable<any> {
|
||||||
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<void> {
|
async setTransaction(transaction, cacheSize: number): Promise<void> {
|
||||||
@@ -62,10 +66,11 @@ export class TransactionService {
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(res) => {
|
(res) => {
|
||||||
transaction.sender = this.getAccountInfo(res.body);
|
transaction.sender = this.getAccountInfo(res, cacheSize);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
transaction.sender = defaultAccount;
|
transaction.sender = defaultAccount;
|
||||||
|
this.userService.addAccount(defaultAccount, cacheSize);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.userService
|
this.userService
|
||||||
@@ -73,10 +78,11 @@ export class TransactionService {
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(res) => {
|
(res) => {
|
||||||
transaction.recipient = this.getAccountInfo(res.body);
|
transaction.recipient = this.getAccountInfo(res, cacheSize);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
transaction.recipient = defaultAccount;
|
transaction.recipient = defaultAccount;
|
||||||
|
this.userService.addAccount(defaultAccount, cacheSize);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -97,10 +103,11 @@ export class TransactionService {
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(res) => {
|
(res) => {
|
||||||
conversion.sender = conversion.recipient = this.getAccountInfo(res.body);
|
conversion.sender = conversion.recipient = this.getAccountInfo(res);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
conversion.sender = conversion.recipient = defaultAccount;
|
conversion.sender = conversion.recipient = defaultAccount;
|
||||||
|
this.userService.addAccount(defaultAccount, cacheSize);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -109,9 +116,16 @@ export class TransactionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addTransaction(transaction, cacheSize: number): void {
|
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);
|
this.transactions.unshift(transaction);
|
||||||
if (this.transactions.length > cacheSize) {
|
if (this.transactions.length > cacheSize) {
|
||||||
this.transactions.length = cacheSize;
|
this.transactions.length = Math.min(this.transactions.length, cacheSize);
|
||||||
}
|
}
|
||||||
this.transactionList.next(this.transactions);
|
this.transactionList.next(this.transactions);
|
||||||
}
|
}
|
||||||
@@ -121,9 +135,10 @@ export class TransactionService {
|
|||||||
this.transactionList.next(this.transactions);
|
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;
|
const accountInfo = Envelope.fromJSON(JSON.stringify(account)).unwrap().m.data;
|
||||||
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
||||||
|
this.userService.addAccount(accountInfo, cacheSize);
|
||||||
return accountInfo;
|
return accountInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,41 +148,43 @@ export class TransactionService {
|
|||||||
recipientAddress: string,
|
recipientAddress: string,
|
||||||
value: number
|
value: number
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const transferAuthAddress = await this.registry.getContractAddressByName(
|
this.registry.onload = async (addressReturned: string): Promise<void> => {
|
||||||
'TransferAuthorization'
|
const transferAuthAddress = await this.registry.getContractAddressByName(
|
||||||
);
|
'TransferAuthorization'
|
||||||
const hashFunction = new Keccak(256);
|
);
|
||||||
hashFunction.update('createRequest(address,address,address,uint256)');
|
const hashFunction = new Keccak(256);
|
||||||
const hash = hashFunction.digest();
|
hashFunction.update('createRequest(address,address,address,uint256)');
|
||||||
const methodSignature = hash.toString('hex').substring(0, 8);
|
const hash = hashFunction.digest();
|
||||||
const abiCoder = new utils.AbiCoder();
|
const methodSignature = hash.toString('hex').substring(0, 8);
|
||||||
const abi = await abiCoder.encode(
|
const abiCoder = new utils.AbiCoder();
|
||||||
['address', 'address', 'address', 'uint256'],
|
const abi = await abiCoder.encode(
|
||||||
[senderAddress, recipientAddress, tokenAddress, value]
|
['address', 'address', 'address', 'uint256'],
|
||||||
);
|
[senderAddress, recipientAddress, tokenAddress, value]
|
||||||
const data = fromHex(methodSignature + strip0x(abi));
|
);
|
||||||
const tx = new Tx(environment.bloxbergChainId);
|
const data = fromHex(methodSignature + strip0x(abi));
|
||||||
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);
|
const tx = new Tx(environment.bloxbergChainId);
|
||||||
tx.gasPrice = Number(await this.web3.eth.getGasPrice());
|
tx.nonce = await this.web3.eth.getTransactionCount(senderAddress);
|
||||||
tx.gasLimit = 8000000;
|
tx.gasPrice = Number(await this.web3.eth.getGasPrice());
|
||||||
tx.to = fromHex(strip0x(transferAuthAddress));
|
tx.gasLimit = 8000000;
|
||||||
tx.value = toValue(value);
|
tx.to = fromHex(strip0x(transferAuthAddress));
|
||||||
tx.data = data;
|
tx.value = toValue(value);
|
||||||
const txMsg = tx.message();
|
tx.data = data;
|
||||||
const privateKey = this.authService.mutableKeyStore.getPrivateKey();
|
const txMsg = tx.message();
|
||||||
if (!privateKey.isDecrypted()) {
|
const privateKey = this.authService.mutableKeyStore.getPrivateKey();
|
||||||
const password = window.prompt('password');
|
if (!privateKey.isDecrypted()) {
|
||||||
await privateKey.decrypt(password);
|
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 signatureObject = secp256k1.ecdsaSign(txMsg, privateKey.keyPacket.privateParams.d);
|
||||||
const s = signatureObject.signature.slice(32);
|
const r = signatureObject.signature.slice(0, 32);
|
||||||
const v = signatureObject.recid;
|
const s = signatureObject.signature.slice(32);
|
||||||
tx.setSignature(r, s, v);
|
const v = signatureObject.recid;
|
||||||
const txWire = add0x(toHex(tx.serializeRLP()));
|
tx.setSignature(r, s, v);
|
||||||
const result = await this.web3.eth.sendSignedTransaction(txWire);
|
const txWire = add0x(toHex(tx.serializeRLP()));
|
||||||
this.loggingService.sendInfoLevelMessage(`Result: ${result}`);
|
const result = await this.web3.eth.sendSignedTransaction(txWire);
|
||||||
const transaction = await this.web3.eth.getTransaction(result.transactionHash);
|
this.loggingService.sendInfoLevelMessage(`Result: ${result}`);
|
||||||
this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`);
|
const transaction = await this.web3.eth.getTransaction(result.transactionHash);
|
||||||
|
this.loggingService.sendInfoLevelMessage(`Transaction: ${transaction}`);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,20 +39,20 @@ export class UserService {
|
|||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private registryService: RegistryService,
|
|
||||||
private authService: AuthService
|
private authService: AuthService
|
||||||
) {
|
) {}
|
||||||
this.authService.init().then(() => {
|
|
||||||
this.keystore = authService.mutableKeyStore;
|
async init(): Promise<void> {
|
||||||
this.signer = new PGPSigner(this.keystore);
|
await this.authService.init();
|
||||||
});
|
await this.tokenService.init();
|
||||||
this.registry = registryService.getRegistry();
|
this.keystore = this.authService.mutableKeyStore;
|
||||||
this.registry.load();
|
this.signer = new PGPSigner(this.keystore);
|
||||||
|
this.registry = await RegistryService.getRegistry();
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPin(phone: string): Observable<any> {
|
resetPin(phone: string): Observable<any> {
|
||||||
const params: HttpParams = new HttpParams().set('phoneNumber', phone);
|
const params: HttpParams = new HttpParams().set('phoneNumber', phone);
|
||||||
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, { params });
|
return this.httpClient.put(`${environment.cicUssdUrl}/pin`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
getAccountStatus(phone: string): Observable<any> {
|
getAccountStatus(phone: string): Observable<any> {
|
||||||
@@ -183,9 +183,7 @@ export class UserService {
|
|||||||
'AccountRegistry'
|
'AccountRegistry'
|
||||||
);
|
);
|
||||||
const accountIndexQuery = new AccountIndex(accountIndexAddress);
|
const accountIndexQuery = new AccountIndex(accountIndexAddress);
|
||||||
const accountAddresses: Array<string> = await accountIndexQuery.last(
|
const accountAddresses: Array<string> = await accountIndexQuery.last(limit);
|
||||||
await accountIndexQuery.totalAccounts()
|
|
||||||
);
|
|
||||||
this.loggingService.sendInfoLevelMessage(accountAddresses);
|
this.loggingService.sendInfoLevelMessage(accountAddresses);
|
||||||
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
||||||
await this.getAccountByAddress(accountAddress, limit);
|
await this.getAccountByAddress(accountAddress, limit);
|
||||||
@@ -203,16 +201,14 @@ export class UserService {
|
|||||||
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||||
const accountInfo = account.m.data;
|
const accountInfo = account.m.data;
|
||||||
await personValidation(accountInfo);
|
await personValidation(accountInfo);
|
||||||
accountInfo.balance = await this.tokenService.getTokenBalance(
|
this.tokenService.onload = async (status: boolean): Promise<void> => {
|
||||||
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
|
accountInfo.balance = await this.tokenService.getTokenBalance(
|
||||||
);
|
accountInfo.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
|
||||||
|
);
|
||||||
|
};
|
||||||
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
||||||
await vcardValidation(accountInfo.vcard);
|
await vcardValidation(accountInfo.vcard);
|
||||||
this.accounts.unshift(accountInfo);
|
this.addAccount(accountInfo, limit);
|
||||||
if (this.accounts.length > limit) {
|
|
||||||
this.accounts.length = limit;
|
|
||||||
}
|
|
||||||
this.accountsList.next(this.accounts);
|
|
||||||
accountSubject.next(accountInfo);
|
accountSubject.next(accountInfo);
|
||||||
});
|
});
|
||||||
return accountSubject.asObservable();
|
return accountSubject.asObservable();
|
||||||
@@ -264,4 +260,23 @@ export class UserService {
|
|||||||
getGenders(): Observable<any> {
|
getGenders(): Observable<any> {
|
||||||
return this.httpClient.get(`${environment.cicMetaUrl}/genders`);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/app/_services/web3.service.spec.ts
Normal file
16
src/app/_services/web3.service.spec.ts
Normal file
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
19
src/app/_services/web3.service.ts
Normal file
19
src/app/_services/web3.service.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1 @@
|
|||||||
<app-network-status></app-network-status>
|
|
||||||
<router-outlet (activate)="onResize(mediaQuery)"></router-outlet>
|
<router-outlet (activate)="onResize(mediaQuery)"></router-outlet>
|
||||||
|
|||||||
@@ -27,28 +27,23 @@ export class AppComponent implements OnInit {
|
|||||||
private errorDialogService: ErrorDialogService,
|
private errorDialogService: ErrorDialogService,
|
||||||
private swUpdate: SwUpdate
|
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.mediaQuery.addEventListener('change', this.onResize);
|
||||||
this.onResize(this.mediaQuery);
|
this.onResize(this.mediaQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
|
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) {
|
if (!this.swUpdate.isEnabled) {
|
||||||
this.swUpdate.available.subscribe(() => {
|
this.swUpdate.available.subscribe(() => {
|
||||||
if (confirm('New Version available. Load New Version?')) {
|
if (confirm('New Version available. Load New Version?')) {
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
<app-network-status></app-network-status>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row justify-content-center mt-5 mb-5">
|
<div class="row justify-content-center mt-5 mb-5">
|
||||||
<div class="col-lg-6 col-md-8 col-sm-10">
|
<div class="col-lg-6 col-md-8 col-sm-10">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<mat-card-title class="card-header pt-4 pb-4 text-center bg-dark">
|
<mat-card-title class="card-header pt-4 pb-4 text-center background-dark">
|
||||||
<a routerLink="/">
|
<a routerLink="/">
|
||||||
<h1 class="text-white">CICADA</h1>
|
<h1 class="text-white">CICADA</h1>
|
||||||
</a>
|
</a>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div id="one" style="display: block" class="card-body p-4 align-items-center">
|
<div id="one" style="display: block" class="card-body p-4">
|
||||||
|
|
||||||
<div class="text-center w-75 m-auto">
|
<div class="text-center w-75 m-auto">
|
||||||
<h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
|
<h4 class="text-dark-50 text-center font-weight-bold">Add Private Key</h4>
|
||||||
@@ -19,13 +20,13 @@
|
|||||||
<mat-label>Private Key</mat-label>
|
<mat-label>Private Key</mat-label>
|
||||||
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
|
<textarea matInput style="height: 30rem" formControlName="key" placeholder="Enter your private key..."
|
||||||
[errorStateMatcher]="matcher"></textarea>
|
[errorStateMatcher]="matcher"></textarea>
|
||||||
<div *ngIf="keyFormSubmitted && keyFormStub.key.errors" class="invalid-feedback">
|
<div *ngIf="submitted && keyFormStub.key.errors" class="invalid-feedback">
|
||||||
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
|
<mat-error *ngIf="keyFormStub.key.errors.required">Private Key is required.</mat-error>
|
||||||
</div>
|
</div>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="keyFormLoading">
|
<button mat-raised-button matRipple color="primary" type="submit" [disabled]="loading">
|
||||||
<span *ngIf="keyFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||||
Add Key
|
Add Key
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -33,43 +34,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="two" style="display: none" class="card-body p-4 align-items-center">
|
<div id="two" style="display: none" class="card-body p-4 align-items-center">
|
||||||
|
|
||||||
<div class="text-center w-75 m-auto">
|
|
||||||
<h4 id="passwordState" class="text-dark-50 text-center font-weight-bold"></h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="center-items">
|
|
||||||
<form [formGroup]="passwordForm" (ngSubmit)="onPasswordInput()">
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Password</mat-label>
|
|
||||||
<input matInput type="password" formControlName="password" placeholder="Enter your private key password..."
|
|
||||||
[errorStateMatcher]="matcher">
|
|
||||||
<div *ngIf="passwordForm && passwordFormStub.password.errors" class="invalid-feedback">
|
|
||||||
<mat-error *ngIf="passwordFormStub.password.errors.required">Private Key password is required.</mat-error>
|
|
||||||
</div>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<button id="passwordLoginButton" mat-raised-button matRipple color="primary" type="submit" class="ml-3" [disabled]="passwordFormLoading">
|
|
||||||
<span *ngIf="passwordFormLoading" class="spinner-border spinner-border-sm mr-1"></span>
|
|
||||||
Login
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mt-3">
|
|
||||||
<div class="col-12 text-center">
|
|
||||||
<p class="text-muted">Change private key?
|
|
||||||
<a (click)="keyInput()" class="text-muted ml-1">
|
|
||||||
<b>Enter private key</b>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div> <!-- end col-->
|
|
||||||
</div>
|
|
||||||
<!-- end row -->
|
|
||||||
</div>
|
|
||||||
<div id="three" style="display: none" class="card-body p-4 align-items-center">
|
|
||||||
|
|
||||||
<div class="text-center w-75 m-auto">
|
<div class="text-center w-75 m-auto">
|
||||||
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
|
<h4 id="state" class="text-dark-50 text-center font-weight-bold"></h4>
|
||||||
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
|
<button mat-raised-button matRipple color="primary" type="submit" (click)="login()"> Login </button>
|
||||||
@@ -77,11 +41,7 @@
|
|||||||
|
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
<div class="col-12 text-center">
|
<div class="col-12 text-center">
|
||||||
<p class="text-muted">Change private key?
|
<p class="text-muted">Change private key? <a (click)="switchWindows()" class="text-muted ml-1"><b>Enter private key</b></a></p>
|
||||||
<a (click)="keyInput()" class="text-muted ml-1">
|
|
||||||
<b>Enter private key</b>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div> <!-- end col-->
|
</div> <!-- end col-->
|
||||||
</div>
|
</div>
|
||||||
<!-- end row -->
|
<!-- end row -->
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
import { CustomErrorStateMatcher } from '@app/_helpers';
|
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||||
import { AuthService } from '@app/_services';
|
import { AuthService } from '@app/_services';
|
||||||
@@ -12,126 +12,68 @@ import { Router } from '@angular/router';
|
|||||||
})
|
})
|
||||||
export class AuthComponent implements OnInit {
|
export class AuthComponent implements OnInit {
|
||||||
keyForm: FormGroup;
|
keyForm: FormGroup;
|
||||||
|
submitted: boolean = false;
|
||||||
|
loading: boolean = false;
|
||||||
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
matcher: CustomErrorStateMatcher = new CustomErrorStateMatcher();
|
||||||
keyFormSubmitted: boolean = false;
|
|
||||||
keyFormLoading: boolean = false;
|
|
||||||
passwordForm: FormGroup;
|
|
||||||
passwordFormSubmitted: boolean = false;
|
|
||||||
passwordFormLoading: boolean = false;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private router: Router,
|
private router: Router
|
||||||
private cdr: ChangeDetectorRef
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.keyForm = this.formBuilder.group({
|
this.keyForm = this.formBuilder.group({
|
||||||
key: ['', Validators.required],
|
key: ['', Validators.required],
|
||||||
});
|
});
|
||||||
this.passwordForm = this.formBuilder.group({
|
await this.authService.init();
|
||||||
password: ['', Validators.required],
|
// if (this.authService.privateKey !== undefined) {
|
||||||
});
|
// const setKey = await this.authService.setKey(this.authService.privateKey);
|
||||||
if (this.authService.privateKey !== undefined) {
|
// }
|
||||||
const setKey = await this.authService.setKey(this.authService.privateKey);
|
// }
|
||||||
if (setKey) {
|
|
||||||
this.passwordInput();
|
|
||||||
}
|
|
||||||
if (setKey && this.authService.sessionToken !== undefined) {
|
|
||||||
this.loginView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get keyFormStub(): any {
|
get keyFormStub(): any {
|
||||||
return this.keyForm.controls;
|
return this.keyForm.controls;
|
||||||
}
|
}
|
||||||
get passwordFormStub(): any {
|
|
||||||
return this.passwordForm.controls;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onSubmit(): Promise<void> {
|
async onSubmit(): Promise<void> {
|
||||||
this.keyFormSubmitted = true;
|
this.submitted = true;
|
||||||
|
|
||||||
if (this.keyForm.invalid) {
|
if (this.keyForm.invalid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.keyFormLoading = true;
|
this.loading = true;
|
||||||
const keySetup = await this.authService.setKey(this.keyFormStub.key.value);
|
await this.authService.setKey(this.keyFormStub.key.value);
|
||||||
if (keySetup) {
|
this.loading = false;
|
||||||
this.passwordInput();
|
|
||||||
}
|
|
||||||
this.keyFormLoading = false;
|
|
||||||
this.cdr.detectChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
async onPasswordInput(): Promise<void> {
|
|
||||||
this.passwordFormSubmitted = true;
|
|
||||||
|
|
||||||
if (this.passwordForm.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.passwordFormLoading = true;
|
|
||||||
const passwordLogin = await this.authService.passwordLogin(
|
|
||||||
this.passwordFormStub.password.value
|
|
||||||
);
|
|
||||||
if (passwordLogin) {
|
|
||||||
this.loginView();
|
|
||||||
}
|
|
||||||
this.passwordFormLoading = false;
|
|
||||||
this.cdr.detectChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
login(): void {
|
login(): void {
|
||||||
if (this.authService.sessionToken === undefined) {
|
// TODO check if we have privatekey
|
||||||
this.passwordInput();
|
// Send us to home if we have a private key
|
||||||
}
|
// talk to meta somehow
|
||||||
const loginStatus = this.authService.login();
|
// in the error interceptor if 401/403 handle it
|
||||||
if (loginStatus) {
|
// if 200 go /home
|
||||||
|
if (this.authService.getPrivateKey()) {
|
||||||
this.router.navigate(['/home']);
|
this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
keyInput(): void {
|
switchWindows(): void {
|
||||||
this.authService.sessionToken = undefined;
|
this.authService.sessionToken = undefined;
|
||||||
this.switchWindows(true, false, false);
|
const divOne: HTMLElement = document.getElementById('one');
|
||||||
|
const divTwo: HTMLElement = document.getElementById('two');
|
||||||
|
this.toggleDisplay(divOne);
|
||||||
|
this.toggleDisplay(divTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordInput(): void {
|
toggleDisplay(element: any): void {
|
||||||
this.authService.sessionToken = undefined;
|
|
||||||
this.switchWindows(false, true, false);
|
|
||||||
this.setPasswordState(
|
|
||||||
'Enter Password to log in with PGP key ' + this.authService.mutableKeyStore.getPrivateKeyId()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
loginView(): void {
|
|
||||||
this.switchWindows(false, false, true);
|
|
||||||
this.authService.setState('Click button to log in');
|
|
||||||
}
|
|
||||||
|
|
||||||
switchWindows(divOneStatus: boolean, divTwoStatus: boolean, divThreeStatus: boolean): void {
|
|
||||||
const divOne = document.getElementById('one');
|
|
||||||
const divTwo = document.getElementById('two');
|
|
||||||
const divThree = document.getElementById('three');
|
|
||||||
this.toggleDisplay(divOne, divOneStatus);
|
|
||||||
this.toggleDisplay(divTwo, divTwoStatus);
|
|
||||||
this.toggleDisplay(divThree, divThreeStatus);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDisplay(element: any, active: boolean): void {
|
|
||||||
const style: string = window.getComputedStyle(element).display;
|
const style: string = window.getComputedStyle(element).display;
|
||||||
if (active) {
|
if (style === 'block') {
|
||||||
element.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
element.style.display = 'none';
|
element.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
element.style.display = 'block';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPasswordState(s): void {
|
|
||||||
document.getElementById('passwordState').innerHTML = s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { MatSelectModule } from '@angular/material/select';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatRippleModule } from '@angular/material/core';
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
import { SharedModule } from '@app/shared/shared.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AuthComponent, PasswordToggleDirective],
|
declarations: [AuthComponent, PasswordToggleDirective],
|
||||||
@@ -22,6 +23,7 @@ import { MatRippleModule } from '@angular/material/core';
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatRippleModule,
|
MatRippleModule,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<strong> {{account?.vcard?.fn[0].value}} </strong>
|
<strong> {{account?.vcard?.fn[0].value}} </strong>
|
||||||
</h3>
|
</h3>
|
||||||
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
|
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
|
||||||
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | date}}</span>
|
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | unixDate}}</span>
|
||||||
<span class="ml-2"><strong>Address:</strong>
|
<span class="ml-2"><strong>Address:</strong>
|
||||||
<a href="{{bloxbergLink}}" target="_blank"> {{accountAddress}} </a>
|
<a href="{{bloxbergLink}}" target="_blank"> {{accountAddress}} </a>
|
||||||
<img src="assets/images/checklist.svg" class="ml-2" height="20rem" (click)="copyAddress()" alt="Copy">
|
<img src="assets/images/checklist.svg" class="ml-2" height="20rem" (click)="copyAddress()" alt="Copy">
|
||||||
@@ -48,10 +48,19 @@
|
|||||||
|
|
||||||
<div class="col-md-6 col-lg-4">
|
<div class="col-md-6 col-lg-4">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label>Name(s): *</mat-label>
|
<mat-label>First Name: *</mat-label>
|
||||||
<input matInput type="text" id="givenNames" placeholder="{{account?.vcard?.fn[0].value}}"
|
<input matInput type="text" id="firstName" placeholder="{{account?.vcard?.fn[0].value.split(' ')[0]}}"
|
||||||
value="{{account?.vcard?.fn[0].value}}" formControlName="name" [errorStateMatcher]="matcher">
|
value="{{account?.vcard?.fn[0].value.split(' ')[0]}}" formControlName="firstName" [errorStateMatcher]="matcher">
|
||||||
<mat-error *ngIf="submitted && accountInfoFormStub.name.errors">Name is required.</mat-error>
|
<mat-error *ngIf="submitted && accountInfoFormStub.firstName.errors">First Name is required.</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Last Name(s): *</mat-label>
|
||||||
|
<input matInput type="text" id="lastName" placeholder="{{account?.vcard?.fn[0].value.split(' ').slice(1).join(' ')}}"
|
||||||
|
value="{{account?.vcard?.fn[0].value.split(' ').slice(1).join(' ')}}" formControlName="lastName" [errorStateMatcher]="matcher">
|
||||||
|
<mat-error *ngIf="submitted && accountInfoFormStub.lastName.errors">Last Name is required.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -210,12 +219,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{account?.vcard?.fn[0].value}}</td>
|
<td>{{account?.vcard?.fn[0].value}}</td>
|
||||||
<td>{{account?.balance | tokenRatio}}</td>
|
<td>{{account?.balance | tokenRatio}}</td>
|
||||||
<td>{{account?.date_registered | date}}</td>
|
<td>{{account?.date_registered | unixDate}}</td>
|
||||||
<td>
|
<td>
|
||||||
<span *ngIf="accountStatus === 'active'" class="badge badge-success badge-pill">
|
<span class="badge badge-success badge-pill">
|
||||||
{{accountStatus}}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="accountStatus === 'blocked'" class="badge badge-danger badge-pill">
|
|
||||||
{{accountStatus}}
|
{{accountStatus}}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -228,7 +234,7 @@
|
|||||||
|
|
||||||
<mat-tab-group *ngIf="account" dynamicHeight mat-align-tabs="start">
|
<mat-tab-group *ngIf="account" dynamicHeight mat-align-tabs="start">
|
||||||
<mat-tab label="Transactions">
|
<mat-tab label="Transactions">
|
||||||
<app-transaction-details [transaction]="transaction"></app-transaction-details>
|
<app-transaction-details [transaction]="transaction" (closeWindow)="transaction = $event"></app-transaction-details>
|
||||||
<div class="card mt-1">
|
<div class="card mt-1">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -252,17 +258,17 @@
|
|||||||
<mat-icon matSuffix>search</mat-icon>
|
<mat-icon matSuffix>search</mat-icon>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-table class="mat-elevation-z10" [dataSource]="transactionsDataSource" matSort matSortActive="created"
|
<table mat-table class="mat-elevation-z10" [dataSource]="transactionsDataSource" matSort matSortActive="created"
|
||||||
#TransactionTableSort="matSort" matSortDirection="asc" matSortDisableClear>
|
#TransactionTableSort="matSort" matSortDirection="asc" matSortDisableClear>
|
||||||
|
|
||||||
<ng-container matColumnDef="sender">
|
<ng-container matColumnDef="sender">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sender </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sender </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value || transaction.from}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="recipient">
|
<ng-container matColumnDef="recipient">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Recipient </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Recipient </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value || transaction.to}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="value">
|
<ng-container matColumnDef="value">
|
||||||
@@ -275,7 +281,7 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="created">
|
<ng-container matColumnDef="created">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Created </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Created </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | date}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | unixDate}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="type">
|
<ng-container matColumnDef="type">
|
||||||
@@ -285,10 +291,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="transactionsDisplayedColumns"></mat-header-row>
|
<tr mat-header-row *matHeaderRowDef="transactionsDisplayedColumns"></tr>
|
||||||
<mat-row *matRowDef="let transaction; columns: transactionsDisplayedColumns" matRipple
|
<tr mat-row *matRowDef="let transaction; columns: transactionsDisplayedColumns" matRipple
|
||||||
(click)="viewTransaction(transaction)"></mat-row>
|
(click)="viewTransaction(transaction)"></tr>
|
||||||
</mat-table>
|
</table>
|
||||||
|
|
||||||
<mat-paginator #TransactionTablePaginator="matPaginator" [pageSize]="transactionsDefaultPageSize"
|
<mat-paginator #TransactionTablePaginator="matPaginator" [pageSize]="transactionsDefaultPageSize"
|
||||||
[pageSizeOptions]="transactionsPageSizeOptions" showFirstLastButtons></mat-paginator>
|
[pageSizeOptions]="transactionsPageSizeOptions" showFirstLastButtons></mat-paginator>
|
||||||
@@ -337,7 +343,7 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="created">
|
<ng-container matColumnDef="created">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let user"> {{user?.date_registered | date}} </mat-cell>
|
<mat-cell *matCellDef="let user"> {{user?.date_registered | unixDate}} </mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="balance">
|
<ng-container matColumnDef="balance">
|
||||||
|
|||||||
@@ -78,8 +78,17 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
private cdr: ChangeDetectorRef,
|
private cdr: ChangeDetectorRef,
|
||||||
private snackBar: MatSnackBar
|
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<void> {
|
||||||
this.accountInfoForm = this.formBuilder.group({
|
this.accountInfoForm = this.formBuilder.group({
|
||||||
name: ['', Validators.required],
|
firstName: ['', Validators.required],
|
||||||
|
lastName: ['', Validators.required],
|
||||||
phoneNumber: ['', Validators.required],
|
phoneNumber: ['', Validators.required],
|
||||||
age: ['', Validators.required],
|
age: ['', Validators.required],
|
||||||
type: ['', Validators.required],
|
type: ['', Validators.required],
|
||||||
@@ -90,36 +99,71 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
location: ['', Validators.required],
|
location: ['', Validators.required],
|
||||||
locationType: ['', Validators.required],
|
locationType: ['', Validators.required],
|
||||||
});
|
});
|
||||||
this.route.paramMap.subscribe(async (params: Params) => {
|
await this.blockSyncService.init();
|
||||||
this.accountAddress = add0x(params.get('id'));
|
await this.tokenService.init();
|
||||||
this.bloxbergLink =
|
await this.transactionService.init();
|
||||||
'https://blockexplorer.bloxberg.org/address/' + this.accountAddress + '/transactions';
|
await this.userService.init();
|
||||||
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
|
await this.blockSyncService.blockSync(this.accountAddress);
|
||||||
async (res) => {
|
this.userService.resetAccountsList();
|
||||||
if (res !== undefined) {
|
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
|
||||||
this.account = res;
|
async (res) => {
|
||||||
this.cdr.detectChanges();
|
if (res !== undefined) {
|
||||||
this.loggingService.sendInfoLevelMessage(this.account);
|
this.account = res;
|
||||||
// this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first())
|
this.cdr.detectChanges();
|
||||||
// .subscribe(response => this.accountStatus = response);
|
this.loggingService.sendInfoLevelMessage(this.account);
|
||||||
this.accountInfoForm.patchValue({
|
const fullName = this.account.vcard?.fn[0].value.split(' ');
|
||||||
name: this.account.vcard?.fn[0].value,
|
this.accountInfoForm.patchValue({
|
||||||
phoneNumber: this.account.vcard?.tel[0].value,
|
firstName: fullName[0],
|
||||||
age: this.account.age,
|
lastName: fullName.slice(1).join(' '),
|
||||||
type: this.account.type,
|
phoneNumber: this.account.vcard?.tel[0].value,
|
||||||
bio: this.account.products,
|
age: this.account.age,
|
||||||
gender: this.account.gender,
|
type: this.account.type,
|
||||||
businessCategory: this.account.category,
|
bio: this.account.products,
|
||||||
userLocation: this.account.location.area_name,
|
gender: this.account.gender,
|
||||||
location: this.account.location.area,
|
businessCategory:
|
||||||
locationType: this.account.location.area_type,
|
this.account.category ||
|
||||||
});
|
this.userService.getCategoryByProduct(this.account.products[0]),
|
||||||
} else {
|
userLocation: this.account.location.area_name,
|
||||||
alert('Account not found!');
|
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<any>(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<any>(transactions);
|
||||||
|
this.transactionsDataSource.paginator = this.transactionTablePaginator;
|
||||||
|
this.transactionsDataSource.sort = this.transactionTableSort;
|
||||||
|
this.transactions = transactions;
|
||||||
|
this.cdr.detectChanges();
|
||||||
});
|
});
|
||||||
this.userService
|
this.userService
|
||||||
.getCategories()
|
.getCategories()
|
||||||
@@ -147,22 +191,6 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
.subscribe((res) => (this.genders = res));
|
.subscribe((res) => (this.genders = res));
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
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.transactionsDataSource = new MatTableDataSource<any>(transactions);
|
|
||||||
this.transactionsDataSource.paginator = this.transactionTablePaginator;
|
|
||||||
this.transactionsDataSource.sort = this.transactionTableSort;
|
|
||||||
this.transactions = transactions;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
doTransactionFilter(value: string): void {
|
doTransactionFilter(value: string): void {
|
||||||
this.transactionsDataSource.filter = value.trim().toLocaleLowerCase();
|
this.transactionsDataSource.filter = value.trim().toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
@@ -192,7 +220,7 @@ export class AccountDetailsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
const accountKey = await this.userService.changeAccountInfo(
|
const accountKey = await this.userService.changeAccountInfo(
|
||||||
this.accountAddress,
|
this.accountAddress,
|
||||||
this.accountInfoFormStub.name.value,
|
this.accountInfoFormStub.firstName.value + ' ' + this.accountInfoFormStub.lastName.value,
|
||||||
this.accountInfoFormStub.phoneNumber.value,
|
this.accountInfoFormStub.phoneNumber.value,
|
||||||
this.accountInfoFormStub.age.value,
|
this.accountInfoFormStub.age.value,
|
||||||
this.accountInfoFormStub.type.value,
|
this.accountInfoFormStub.type.value,
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ export class AccountSearchComponent implements OnInit {
|
|||||||
private router: Router
|
private router: Router
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.userService.init();
|
||||||
this.nameSearchForm = this.formBuilder.group({
|
this.nameSearchForm = this.formBuilder.group({
|
||||||
name: ['', Validators.required],
|
name: ['', Validators.required],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="created">
|
<ng-container matColumnDef="created">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> CREATED </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let user"> {{user?.date_registered | date}} </mat-cell>
|
<mat-cell *matCellDef="let user"> {{user?.date_registered | unixDate}} </mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="balance">
|
<ng-container matColumnDef="balance">
|
||||||
|
|||||||
@@ -32,28 +32,26 @@ export class AccountsComponent implements OnInit {
|
|||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private loggingService: LoggingService,
|
private loggingService: LoggingService,
|
||||||
private router: Router
|
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<void> {
|
||||||
|
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.userService.accountsSubject.subscribe((accounts) => {
|
||||||
this.dataSource = new MatTableDataSource<any>(accounts);
|
this.dataSource = new MatTableDataSource<any>(accounts);
|
||||||
this.dataSource.paginator = this.paginator;
|
this.dataSource.paginator = this.paginator;
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
this.accounts = accounts;
|
this.accounts = accounts;
|
||||||
});
|
});
|
||||||
|
this.userService
|
||||||
|
.getAccountTypes()
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((res) => (this.accountTypes = res));
|
||||||
}
|
}
|
||||||
|
|
||||||
doFilter(value: string): void {
|
doFilter(value: string): void {
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ export class CreateAccountComponent implements OnInit {
|
|||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.userService.init();
|
||||||
this.createForm = this.formBuilder.group({
|
this.createForm = this.formBuilder.group({
|
||||||
accountType: ['', Validators.required],
|
accountType: ['', Validators.required],
|
||||||
idNumber: ['', Validators.required],
|
idNumber: ['', Validators.required],
|
||||||
|
|||||||
@@ -30,7 +30,10 @@ export class AdminComponent implements OnInit {
|
|||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
constructor(private userService: UserService, private loggingService: LoggingService) {
|
constructor(private userService: UserService, private loggingService: LoggingService) {}
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.userService.init();
|
||||||
this.userService.getActions();
|
this.userService.getActions();
|
||||||
this.userService.actionsSubject.subscribe((actions) => {
|
this.userService.actionsSubject.subscribe((actions) => {
|
||||||
this.dataSource = new MatTableDataSource<any>(actions);
|
this.dataSource = new MatTableDataSource<any>(actions);
|
||||||
@@ -40,8 +43,6 @@ export class AdminComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
|
||||||
|
|
||||||
doFilter(value: string): void {
|
doFilter(value: string): void {
|
||||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,10 @@
|
|||||||
SETTINGS
|
SETTINGS
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4>Kobo Toolbox Credentials</h4>
|
<h4>CICADA Admin Credentials</h4>
|
||||||
<span><strong>Username: </strong> admin_reserve </span><br>
|
<span><strong>UserId: </strong> {{ userInfo?.userid }} </span><br>
|
||||||
<span><strong>Password: </strong> ******** </span>
|
<span><strong>Username: </strong> {{ userInfo?.name }} </span><br>
|
||||||
|
<span><strong>Email: </strong> {{ userInfo?.email }} </span>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@@ -17,19 +17,22 @@ export class SettingsComponent implements OnInit {
|
|||||||
dataSource: MatTableDataSource<any>;
|
dataSource: MatTableDataSource<any>;
|
||||||
displayedColumns: Array<string> = ['name', 'email', 'userId'];
|
displayedColumns: Array<string> = ['name', 'email', 'userId'];
|
||||||
trustedUsers: Array<Staff>;
|
trustedUsers: Array<Staff>;
|
||||||
|
userInfo: Staff;
|
||||||
|
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
|
|
||||||
constructor(private authService: AuthService) {}
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
const d = new Date();
|
await this.authService.init();
|
||||||
this.date = `${d.getDate()}/${d.getMonth()}/${d.getFullYear()}`;
|
this.authService.trustedUsersSubject.subscribe((users) => {
|
||||||
this.trustedUsers = this.authService.getTrustedUsers();
|
this.dataSource = new MatTableDataSource<any>(users);
|
||||||
this.dataSource = new MatTableDataSource<any>(this.trustedUsers);
|
this.dataSource.paginator = this.paginator;
|
||||||
this.dataSource.paginator = this.paginator;
|
this.dataSource.sort = this.sort;
|
||||||
this.dataSource.sort = this.sort;
|
this.trustedUsers = users;
|
||||||
|
});
|
||||||
|
this.userInfo = this.authService.getPrivateKeyInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
doFilter(value: string): void {
|
doFilter(value: string): void {
|
||||||
|
|||||||
@@ -1,60 +1,36 @@
|
|||||||
<!-- Begin page -->
|
<div *ngIf="token" class="mb-3 mt-1">
|
||||||
<div class="wrapper">
|
<div class="card text-center">
|
||||||
<app-sidebar></app-sidebar>
|
<mat-card-title class="card-header">
|
||||||
|
<div class="row">
|
||||||
<!-- ============================================================== -->
|
TOKEN DETAILS
|
||||||
<!-- Start Page Content here -->
|
<button mat-raised-button type="button" class="btn btn-outline-secondary ml-auto mr-2" (click)="close()"> CLOSE </button>
|
||||||
<!-- ============================================================== -->
|
</div>
|
||||||
|
</mat-card-title>
|
||||||
<div id="content">
|
<div class="card-body">
|
||||||
<app-topbar></app-topbar>
|
<div>
|
||||||
<!-- Start Content-->
|
<span><strong>Name:</strong> {{token?.name}}</span>
|
||||||
<div class="container-fluid text-center" appMenuSelection>
|
</div>
|
||||||
<nav aria-label="breadcrumb">
|
<div>
|
||||||
<ol class="breadcrumb">
|
<span><strong>Symbol:</strong> {{token?.symbol}}</span>
|
||||||
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
</div>
|
||||||
<li class="breadcrumb-item"><a routerLink="/tokens">Tokens</a></li>
|
<div>
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{token.name}}</li>
|
<span><strong>Address:</strong> {{token?.address}}</span>
|
||||||
</ol>
|
</div>
|
||||||
</nav>
|
<div>
|
||||||
<div class="col-md-6 center-body">
|
<span><strong>Details:</strong> A community inclusive currency for trading among lower to middle income societies.</span>
|
||||||
<div class="card">
|
</div>
|
||||||
<mat-card-title class="card-header">
|
<div>
|
||||||
Token
|
<span><strong>Supply:</strong> {{token?.supply | tokenRatio}}</span>
|
||||||
</mat-card-title>
|
</div><br>
|
||||||
<div class="card-body">
|
<div>
|
||||||
<div>
|
<h2>Reserve</h2>
|
||||||
<span><strong>Name:</strong> {{token.name}}</span>
|
<div>
|
||||||
</div>
|
<span><strong>Weight:</strong> {{token?.reserveRatio}}</span>
|
||||||
<div>
|
</div>
|
||||||
<span><strong>Symbol:</strong> {{token.symbol}}</span>
|
<div>
|
||||||
</div>
|
<span><strong>Owner:</strong> {{token?.owner}}</span>
|
||||||
<div>
|
|
||||||
<span><strong>Address:</strong> {{token.address}}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span><strong>Details:</strong> A community inclusive currency for trading among lower to middle income societies.</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span><strong>Supply:</strong> {{token.supply | tokenRatio}}</span>
|
|
||||||
</div><br>
|
|
||||||
<div>
|
|
||||||
<h2>Reserve</h2>
|
|
||||||
<div>
|
|
||||||
<span><strong>Weight:</strong> {{token.reserveRatio}}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span><strong>Owner:</strong> {{token.owner}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-footer appMenuSelection></app-footer>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- ============================================================== -->
|
|
||||||
<!-- End Page content -->
|
|
||||||
<!-- ============================================================== -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import {
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
ChangeDetectionStrategy,
|
||||||
import { TokenService } from '@app/_services';
|
Component,
|
||||||
import { first } from 'rxjs/operators';
|
EventEmitter,
|
||||||
import { Token } from '../../../_models';
|
Input,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { Token } from '@app/_models';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-token-details',
|
selector: 'app-token-details',
|
||||||
@@ -11,18 +15,16 @@ import { Token } from '../../../_models';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class TokenDetailsComponent implements OnInit {
|
export class TokenDetailsComponent implements OnInit {
|
||||||
token: Token;
|
@Input() token: Token;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private tokenService: TokenService) {
|
@Output() closeWindow: EventEmitter<any> = new EventEmitter<any>();
|
||||||
this.route.paramMap.subscribe((params: Params) => {
|
|
||||||
this.tokenService
|
constructor() {}
|
||||||
.getTokenBySymbol(params.get('id'))
|
|
||||||
.pipe(first())
|
|
||||||
.subscribe((res) => {
|
|
||||||
this.token = res;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.token = null;
|
||||||
|
this.closeWindow.emit(this.token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
|
<app-token-details [token]="token" (closeWindow)="token = $event"></app-token-details>
|
||||||
|
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
<mat-label> Filter </mat-label>
|
<mat-label> Filter </mat-label>
|
||||||
<input matInput type="text" (keyup)="doFilter($event.target.value)" placeholder="Filter">
|
<input matInput type="text" (keyup)="doFilter($event.target.value)" placeholder="Filter">
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { LoggingService, TokenService } from '@app/_services';
|
|||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { exportCsv } from '@app/_helpers';
|
import { exportCsv } from '@app/_helpers';
|
||||||
import { TokenRegistry } from '../../_eth';
|
import { Token } from '@app/_models';
|
||||||
import { Token } from '../../_models';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tokens',
|
selector: 'app-tokens',
|
||||||
@@ -19,7 +18,8 @@ export class TokensComponent implements OnInit {
|
|||||||
columnsToDisplay: Array<string> = ['name', 'symbol', 'address', 'supply'];
|
columnsToDisplay: Array<string> = ['name', 'symbol', 'address', 'supply'];
|
||||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||||
@ViewChild(MatSort) sort: MatSort;
|
@ViewChild(MatSort) sort: MatSort;
|
||||||
tokens: Array<Promise<string>>;
|
tokens: Array<Token>;
|
||||||
|
token: Token;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
@@ -28,22 +28,25 @@ export class TokensComponent implements OnInit {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.tokenService.LoadEvent.subscribe(async () => {
|
await this.tokenService.init();
|
||||||
this.tokens = await this.tokenService.getTokens();
|
this.tokenService.onload = async (status: boolean): Promise<void> => {
|
||||||
|
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 {
|
doFilter(value: string): void {
|
||||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewToken(token): Promise<void> {
|
viewToken(token): void {
|
||||||
await this.router.navigateByUrl(`/tokens/${token.symbol}`);
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadCsv(): void {
|
downloadCsv(): void {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<div *ngIf="transaction | async" class="mb-3 mt-1">
|
<div *ngIf="transaction" class="mb-3 mt-1">
|
||||||
<div class="card text-center">
|
<div class="card text-center">
|
||||||
<mat-card-title class="card-header">
|
<mat-card-title class="card-header">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
TRANSACTION DETAILS
|
TRANSACTION DETAILS
|
||||||
<button mat-raised-button type="button" class="btn btn-outline-secondary ml-auto mr-2" (click)="transaction = null"> CLOSE </button>
|
<button mat-raised-button type="button" class="btn btn-outline-secondary ml-auto mr-2" (click)="close()"> CLOSE </button>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div *ngIf="transaction.type == 'transaction'" class="card-body">
|
<div *ngIf="transaction.type == 'transaction'" class="card-body">
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<span>Success: {{transaction.tx.success}}</span>
|
<span>Success: {{transaction.tx.success}}</span>
|
||||||
</li>
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<span>Timestamp: {{transaction.tx.timestamp | date}}</span>
|
<span>Timestamp: {{transaction.tx.timestamp | unixDate}}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul><br>
|
</ul><br>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@@ -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 { Router } from '@angular/router';
|
||||||
import { TransactionService } from '@app/_services';
|
import { TransactionService } from '@app/_services';
|
||||||
import { copyToClipboard } from '@app/_helpers';
|
import { copyToClipboard } from '@app/_helpers';
|
||||||
@@ -13,6 +20,9 @@ import { strip0x } from '@src/assets/js/ethtx/dist/hex';
|
|||||||
})
|
})
|
||||||
export class TransactionDetailsComponent implements OnInit {
|
export class TransactionDetailsComponent implements OnInit {
|
||||||
@Input() transaction;
|
@Input() transaction;
|
||||||
|
|
||||||
|
@Output() closeWindow: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
senderBloxbergLink: string;
|
senderBloxbergLink: string;
|
||||||
recipientBloxbergLink: string;
|
recipientBloxbergLink: string;
|
||||||
traderBloxbergLink: string;
|
traderBloxbergLink: string;
|
||||||
@@ -23,7 +33,8 @@ export class TransactionDetailsComponent implements OnInit {
|
|||||||
private snackBar: MatSnackBar
|
private snackBar: MatSnackBar
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.transactionService.init();
|
||||||
if (this.transaction?.type === 'conversion') {
|
if (this.transaction?.type === 'conversion') {
|
||||||
this.traderBloxbergLink =
|
this.traderBloxbergLink =
|
||||||
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions';
|
'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 });
|
this.snackBar.open(address + ' copied successfully!', 'Close', { duration: 3000 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
this.transaction = null;
|
||||||
|
this.closeWindow.emit(this.transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</mat-card-title>
|
</mat-card-title>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<app-transaction-details [transaction]="transaction"></app-transaction-details>
|
<app-transaction-details [transaction]="transaction" (closeWindow)="transaction = $event"></app-transaction-details>
|
||||||
|
|
||||||
<div class="row card-header">
|
<div class="row card-header">
|
||||||
<mat-form-field appearance="outline">
|
<mat-form-field appearance="outline">
|
||||||
@@ -48,12 +48,12 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="sender">
|
<ng-container matColumnDef="sender">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sender </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sender </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.sender?.vcard.fn[0].value || transaction.from}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="recipient">
|
<ng-container matColumnDef="recipient">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Recipient </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Recipient </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.recipient?.vcard.fn[0].value || transaction.to}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="value">
|
<ng-container matColumnDef="value">
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="created">
|
<ng-container matColumnDef="created">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Created </th>
|
<th mat-header-cell *matHeaderCellDef mat-sort-header> Created </th>
|
||||||
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | date}} </td>
|
<td mat-cell *matCellDef="let transaction"> {{transaction?.tx.timestamp | unixDate}} </td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="type">
|
<ng-container matColumnDef="type">
|
||||||
|
|||||||
@@ -36,17 +36,19 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
|
|||||||
private blockSyncService: BlockSyncService,
|
private blockSyncService: BlockSyncService,
|
||||||
private transactionService: TransactionService,
|
private transactionService: TransactionService,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {
|
) {}
|
||||||
this.blockSyncService.blockSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
async ngOnInit(): Promise<void> {
|
||||||
this.transactionService.transactionsSubject.subscribe((transactions) => {
|
this.transactionService.transactionsSubject.subscribe((transactions) => {
|
||||||
this.transactionDataSource = new MatTableDataSource<any>(transactions);
|
this.transactionDataSource = new MatTableDataSource<any>(transactions);
|
||||||
this.transactionDataSource.paginator = this.paginator;
|
this.transactionDataSource.paginator = this.paginator;
|
||||||
this.transactionDataSource.sort = this.sort;
|
this.transactionDataSource.sort = this.sort;
|
||||||
this.transactions = transactions;
|
this.transactions = transactions;
|
||||||
});
|
});
|
||||||
|
await this.blockSyncService.init();
|
||||||
|
await this.transactionService.init();
|
||||||
|
await this.userService.init();
|
||||||
|
await this.blockSyncService.blockSync();
|
||||||
this.userService
|
this.userService
|
||||||
.getTransactionTypes()
|
.getTransactionTypes()
|
||||||
.pipe(first())
|
.pipe(first())
|
||||||
|
|||||||
8
src/app/shared/_pipes/unix-date.pipe.spec.ts
Normal file
8
src/app/shared/_pipes/unix-date.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { UnixDatePipe } from './unix-date.pipe';
|
||||||
|
|
||||||
|
describe('UnixDatePipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new UnixDatePipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
10
src/app/shared/_pipes/unix-date.pipe.ts
Normal file
10
src/app/shared/_pipes/unix-date.pipe.ts
Normal file
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<!-- Footer Start -->
|
<!-- Footer Start -->
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
2020 © Grassroots Economics
|
<a target="blank" title="GPL-3" href="https://www.gnu.org/licenses/gpl-3.0.en.html"> Copyleft </a> 🄯.
|
||||||
|
{{ currentYear }}
|
||||||
|
<a target="blank" title="Gitlab@GrassrootsEconomics" href="https://gitlab.com/grassrootseconomics"><u> Grassroots Economics </u></a>
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
<!-- end Footer -->
|
<!-- end Footer -->
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FooterComponent implements OnInit {
|
export class FooterComponent implements OnInit {
|
||||||
|
currentYear = new Date().getFullYear();
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<nav class="navbar navbar-dark bg-dark">
|
<nav class="navbar navbar-dark background-dark">
|
||||||
<h1 class="navbar-brand">
|
<h1 class="navbar-brand">
|
||||||
<div *ngIf="noInternetConnection; then offlineBlock else onlineBlock"></div>
|
<div *ngIf="noInternetConnection; then offlineBlock else onlineBlock"></div>
|
||||||
<ng-template #offlineBlock>
|
<ng-template #offlineBlock>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { ErrorDialogComponent } from '@app/shared/error-dialog/error-dialog.comp
|
|||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { SafePipe } from '@app/shared/_pipes/safe.pipe';
|
import { SafePipe } from '@app/shared/_pipes/safe.pipe';
|
||||||
import { NetworkStatusComponent } from './network-status/network-status.component';
|
import { NetworkStatusComponent } from './network-status/network-status.component';
|
||||||
|
import { UnixDatePipe } from './_pipes/unix-date.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -24,6 +25,7 @@ import { NetworkStatusComponent } from './network-status/network-status.componen
|
|||||||
ErrorDialogComponent,
|
ErrorDialogComponent,
|
||||||
SafePipe,
|
SafePipe,
|
||||||
NetworkStatusComponent,
|
NetworkStatusComponent,
|
||||||
|
UnixDatePipe,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
TopbarComponent,
|
TopbarComponent,
|
||||||
@@ -33,6 +35,7 @@ import { NetworkStatusComponent } from './network-status/network-status.componen
|
|||||||
TokenRatioPipe,
|
TokenRatioPipe,
|
||||||
SafePipe,
|
SafePipe,
|
||||||
NetworkStatusComponent,
|
NetworkStatusComponent,
|
||||||
|
UnixDatePipe,
|
||||||
],
|
],
|
||||||
imports: [CommonModule, RouterModule, MatIconModule, MatDialogModule],
|
imports: [CommonModule, RouterModule, MatIconModule, MatDialogModule],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<!-- ========== Left Sidebar Start ========== -->
|
<!-- ========== Left Sidebar Start ========== -->
|
||||||
<div id="sidebar">
|
<div id="sidebar">
|
||||||
|
<app-network-status></app-network-status>
|
||||||
<nav>
|
<nav>
|
||||||
|
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
|
|||||||
BIN
src/assets/icons/manifest-icon-512.ico
Normal file
BIN
src/assets/icons/manifest-icon-512.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -42,7 +42,7 @@ Driver.prototype.sync = function (n) {
|
|||||||
const processor = async (b, t) => {
|
const processor = async (b, t) => {
|
||||||
return await self.process(b, t);
|
return await self.process(b, t);
|
||||||
};
|
};
|
||||||
self.syncer(self, self.lo, self.hi, self.filters[0], self.filters[1], countGetter, processor);
|
self.syncer(self.lo, self.hi, self.filters[0], self.filters[1], countGetter, processor);
|
||||||
};
|
};
|
||||||
|
|
||||||
Driver.prototype.process = function (b, t) {
|
Driver.prototype.process = function (b, t) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { hobaResult, hobaToSign } from '@src/assets/js/hoba.js';
|
|||||||
|
|
||||||
const alg = '969';
|
const alg = '969';
|
||||||
|
|
||||||
export async function signChallenge(challenge, realm, origin, keyStore, password) {
|
export async function signChallenge(challenge, realm, origin, keyStore) {
|
||||||
const fingerprint = keyStore.getFingerprint();
|
const fingerprint = keyStore.getFingerprint();
|
||||||
const nonce_array = new Uint8Array(32);
|
const nonce_array = new Uint8Array(32);
|
||||||
crypto.getRandomValues(nonce_array);
|
crypto.getRandomValues(nonce_array);
|
||||||
@@ -14,7 +14,7 @@ export async function signChallenge(challenge, realm, origin, keyStore, password
|
|||||||
const a_challenge = btoa(challenge);
|
const a_challenge = btoa(challenge);
|
||||||
const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg);
|
const message = hobaToSign(a_nonce, a_kid, a_challenge, realm, origin, alg);
|
||||||
|
|
||||||
const signature = await keyStore.sign(message, password);
|
const signature = await keyStore.sign(message);
|
||||||
const a_signature = btoa(signature);
|
const a_signature = btoa(signature);
|
||||||
|
|
||||||
const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
|
const result = hobaResult(a_nonce, a_kid, a_challenge, a_signature);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const environment = {
|
|||||||
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
||||||
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
||||||
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
||||||
cicUssdUrl: 'https://ussd.dev.grassrootseconomics.net',
|
cicUssdUrl: 'https://user.dev.grassrootseconomics.net',
|
||||||
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
||||||
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const environment = {
|
|||||||
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
||||||
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
||||||
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
||||||
cicUssdUrl: 'https://ussd.dev.grassrootseconomics.net',
|
cicUssdUrl: 'https://user.dev.grassrootseconomics.net',
|
||||||
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
||||||
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const environment = {
|
|||||||
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
||||||
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
||||||
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
||||||
cicUssdUrl: 'https://ussd.dev.grassrootseconomics.net',
|
cicUssdUrl: 'https://user.dev.grassrootseconomics.net',
|
||||||
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
||||||
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>CICADA</title>
|
<title>CICADA</title>
|
||||||
<base href="/">
|
<base href="./">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta content="A fully featured admin client for managing users and transactions in the CIC network." name="description"/>
|
<meta content="A fully featured admin client for managing users and transactions in the CIC network." name="description"/>
|
||||||
<meta content="Spencer Ofwiti" name="author"/>
|
<meta content="Spencer Ofwiti" name="author"/>
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||||
<link rel="icon" type="image/x-icon" href="assets/icons/manifest-icon-512.png">
|
<link rel="icon" type="image/x-icon" href="assets/icons/manifest-icon-512.png">
|
||||||
<link rel="apple-touch-icon" href="assets/icons/apple-icon-180.png">
|
<link rel="apple-touch-icon" href="assets/icons/apple-icon-180.png">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ body {
|
|||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-dark {
|
.background-dark {
|
||||||
background: #313a46;
|
background: #313a46 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@@ -65,12 +65,6 @@ footer.footer { color: black; }
|
|||||||
left: -9999px;
|
left: -9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center-items {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar {
|
#sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
position: -webkit-sticky;
|
position: -webkit-sticky;
|
||||||
@@ -213,11 +207,6 @@ a[data-toggle="collapse"] { position: relative; }
|
|||||||
|
|
||||||
.mat-column-select { overflow: initial; }
|
.mat-column-select { overflow: initial; }
|
||||||
|
|
||||||
#passwordLoginButton {
|
|
||||||
height: 3.0rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
button { height: 2.5rem; }
|
button { height: 2.5rem; }
|
||||||
|
|
||||||
.badge-pill { width: 5rem; }
|
.badge-pill { width: 5rem; }
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"target": "es2015",
|
"target": "es5",
|
||||||
"module": "es2020",
|
"module": "es2020",
|
||||||
|
"typeRoots": ["node_modules/@types"],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2018",
|
"es2018",
|
||||||
"dom"
|
"dom"
|
||||||
|
|||||||
Reference in New Issue
Block a user