Compare commits
16 Commits
spencer/fi
...
mono-repo-
| Author | SHA1 | Date | |
|---|---|---|---|
| 4163acfd84 | |||
| 85bd92a2fc | |||
|
|
e79afe3e88 | ||
|
|
473be6b641 | ||
| 742c589835 | |||
| 7ae0413991 | |||
| a4813152ed | |||
|
|
0acb94ce98 | ||
|
|
858e1e65bd | ||
|
|
6298f9a105 | ||
|
|
4ba9f959fe | ||
|
|
a6e80fba64 | ||
|
|
56a3d79aea | ||
|
|
6ce332a9aa | ||
| 0ee8c7e6c8 | |||
| 09c45aa2e5 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/tests
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
@@ -48,4 +49,3 @@ Thumbs.db
|
||||
|
||||
# Configuration Files
|
||||
.env
|
||||
/src/environments/environment.ts
|
||||
|
||||
@@ -13,8 +13,9 @@ RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG FRONTEND_ENV=prod
|
||||
# running build script
|
||||
RUN npm run build:dev
|
||||
RUN npm run build:${FRONTEND_ENV}
|
||||
|
||||
### STAGE 2: Setup ###
|
||||
# defining nginx image version
|
||||
|
||||
30
angular.json
30
angular.json
@@ -24,11 +24,7 @@
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
|
||||
"styles": [
|
||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss",
|
||||
@@ -37,7 +33,8 @@
|
||||
"scripts": [
|
||||
"node_modules/jquery/dist/jquery.js",
|
||||
"node_modules/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
],
|
||||
"webWorkerTsConfig": "tsconfig.worker.json"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@@ -77,6 +74,14 @@
|
||||
"with": "src/environments/environment.dev.ts"
|
||||
}
|
||||
]
|
||||
},
|
||||
"staging": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.staging.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -108,11 +113,7 @@
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"codeCoverage": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets",
|
||||
"src/manifest.webmanifest"
|
||||
],
|
||||
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
|
||||
"styles": [
|
||||
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
|
||||
"src/styles.scss"
|
||||
@@ -126,11 +127,10 @@
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
"e2e/tsconfig.json",
|
||||
"tsconfig.worker.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
"exclude": ["**/node_modules/**"]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
|
||||
371
docs/compodoc/interceptors/ConnectionInterceptor.html
Normal file
371
docs/compodoc/interceptors/ConnectionInterceptor.html
Normal file
@@ -0,0 +1,371 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<title>CICADA</title>
|
||||
<meta name="description" content="">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="../images/favicon.ico">
|
||||
<link rel="stylesheet" href="../styles/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-default navbar-fixed-top visible-xs">
|
||||
<a href="../" class="navbar-brand">CICADA</a>
|
||||
<button type="button" class="btn btn-default btn-menu ion-ios-menu" id="btn-menu"></button>
|
||||
</div>
|
||||
|
||||
<div class="xs-menu menu" id="mobile-menu">
|
||||
<div id="book-search-input" role="search"><input type="text" placeholder="Type to search"></div> <compodoc-menu></compodoc-menu>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid main">
|
||||
<div class="row main">
|
||||
<div class="hidden-xs menu">
|
||||
<compodoc-menu mode="normal"></compodoc-menu>
|
||||
</div>
|
||||
<!-- START CONTENT -->
|
||||
<div class="content interceptor">
|
||||
<div class="content-data">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<ol class="breadcrumb">
|
||||
<li>Interceptors</li>
|
||||
<li>ConnectionInterceptor</li>
|
||||
</ol>
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="active">
|
||||
<a href="#info" role="tab" id="info-tab" data-toggle="tab" data-link="info">Info</a>
|
||||
</li>
|
||||
<li >
|
||||
<a href="#source" role="tab" id="source-tab" data-toggle="tab" data-link="source">Source</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade active in" id="c-info">
|
||||
<p class="comment">
|
||||
<h3>File</h3>
|
||||
</p>
|
||||
<p class="comment">
|
||||
<code>src/app/_interceptors/connection.interceptor.ts</code>
|
||||
</p>
|
||||
|
||||
<p class="comment">
|
||||
<h3>Description</h3>
|
||||
</p>
|
||||
<p class="comment">
|
||||
<p>Intercepts and handles of events from outgoing HTTP request. </p>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
<section>
|
||||
<h3 id="index">Index</h3>
|
||||
<table class="table table-sm table-bordered index-table">
|
||||
<tbody>
|
||||
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<h6><b>Methods</b></h6>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<ul class="index-list">
|
||||
<li>
|
||||
<a href="#intercept">intercept</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 id="constructor">Constructor</h3>
|
||||
<table class="table table-sm table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<code>constructor(loggingService: <a href="../injectables/LoggingService.html">LoggingService</a>)</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<div class="io-line">Defined in <a href="" data-line="14" class="link-to-prism">src/app/_interceptors/connection.interceptor.ts:14</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<div class="io-description"><p>Initialization of the connection interceptor.</p>
|
||||
</div>
|
||||
<div>
|
||||
<b>Parameters :</b>
|
||||
<table class="params">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Type</td>
|
||||
<td>Optional</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>loggingService</td>
|
||||
|
||||
<td>
|
||||
<code><a href="../injectables/LoggingService.html" target="_self" >LoggingService</a></code>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<code><ul>
|
||||
<li>A service that provides logging capabilities.</li>
|
||||
</ul>
|
||||
</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
||||
<h3 id="methods">
|
||||
Methods
|
||||
</h3>
|
||||
<table class="table table-sm table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<a name="intercept"></a>
|
||||
<span class="name">
|
||||
<b>
|
||||
intercept
|
||||
</b>
|
||||
<a href="#intercept"><span class="icon ion-ios-link"></span></a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<code>intercept(request: HttpRequest<unknown>, next: <a href="https://angular.io/api/common/http/HttpHandler" target="_blank">HttpHandler</a>)</code>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<div class="io-line">Defined in <a href="" data-line="29"
|
||||
class="link-to-prism">src/app/_interceptors/connection.interceptor.ts:29</a></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td class="col-md-4">
|
||||
<div class="io-description"><p>Intercepts HTTP requests.</p>
|
||||
</div>
|
||||
|
||||
<div class="io-description">
|
||||
<b>Parameters :</b>
|
||||
<table class="params">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Type</td>
|
||||
<td>Optional</td>
|
||||
<td>Description</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>request</td>
|
||||
<td>
|
||||
<code>HttpRequest<unknown></code>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<ul>
|
||||
<li>An outgoing HTTP request with an optional typed body.</li>
|
||||
</ul>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>next</td>
|
||||
<td>
|
||||
<code><a href="https://angular.io/api/common/http/HttpHandler" target="_blank" >HttpHandler</a></code>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
No
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<ul>
|
||||
<li>The next HTTP handler or the outgoing request dispatcher.</li>
|
||||
</ul>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
</div>
|
||||
<div class="io-description">
|
||||
<b>Returns : </b> <code>Observable<HttpEvent<unknown>></code>
|
||||
|
||||
</div>
|
||||
<div class="io-description">
|
||||
<p>The forwarded request.</p>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="tab-pane fade tab-source-code" id="c-source">
|
||||
<pre class="line-numbers compodoc-sourcecode"><code class="language-typescript">import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// Third party imports
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// Application imports
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { checkOnlineStatus } from '@app/_helpers';
|
||||
|
||||
/** Intercepts and handles of events from outgoing HTTP request. */
|
||||
@Injectable()
|
||||
export class ConnectionInterceptor implements HttpInterceptor {
|
||||
/**
|
||||
* Initialization of the connection interceptor.
|
||||
*
|
||||
* @param loggingService - A service that provides logging capabilities.
|
||||
*/
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
|
||||
/**
|
||||
* Intercepts HTTP requests.
|
||||
*
|
||||
* @param request - An outgoing HTTP request with an optional typed body.
|
||||
* @param next - The next HTTP handler or the outgoing request dispatcher.
|
||||
* @returns The forwarded request.
|
||||
*/
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
checkOnlineStatus().then((online) => {
|
||||
if (!online) {
|
||||
this.loggingService.sendErrorLevelMessage('No internet connection on device!', this, {
|
||||
error: `NetworkError when attempting to fetch resource ${request.url}.`,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
return next.handle(request);
|
||||
}
|
||||
});
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div><div class="search-results">
|
||||
<div class="has-results">
|
||||
<h1 class="search-results-title"><span class='search-results-count'></span> result-matching "<span class='search-query'></span>"</h1>
|
||||
<ul class="search-results-list"></ul>
|
||||
</div>
|
||||
<div class="no-results">
|
||||
<h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END CONTENT -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var COMPODOC_CURRENT_PAGE_DEPTH = 1;
|
||||
var COMPODOC_CURRENT_PAGE_CONTEXT = 'interceptor';
|
||||
var COMPODOC_CURRENT_PAGE_URL = 'ConnectionInterceptor.html';
|
||||
var MAX_SEARCH_RESULTS = 15;
|
||||
</script>
|
||||
|
||||
<script src="../js/libs/custom-elements.min.js"></script>
|
||||
<script src="../js/libs/lit-html.js"></script>
|
||||
<!-- Required to polyfill modern browsers as code is ES5 for IE... -->
|
||||
<script src="../js/libs/custom-elements-es5-adapter.js" charset="utf-8" defer></script>
|
||||
<script src="../js/menu-wc.js" defer></script>
|
||||
|
||||
<script src="../js/libs/bootstrap-native.js"></script>
|
||||
|
||||
<script src="../js/libs/es6-shim.min.js"></script>
|
||||
<script src="../js/libs/EventDispatcher.js"></script>
|
||||
<script src="../js/libs/promise.min.js"></script>
|
||||
<script src="../js/libs/zepto.min.js"></script>
|
||||
|
||||
<script src="../js/compodoc.js"></script>
|
||||
|
||||
<script src="../js/tabs.js"></script>
|
||||
<script src="../js/menu.js"></script>
|
||||
<script src="../js/libs/clipboard.min.js"></script>
|
||||
<script src="../js/libs/prism.js"></script>
|
||||
<script src="../js/sourceCode.js"></script>
|
||||
<script src="../js/search/search.js"></script>
|
||||
<script src="../js/search/lunr.min.js"></script>
|
||||
<script src="../js/search/search-lunr.js"></script>
|
||||
<script src="../js/search/search_index.js"></script>
|
||||
<script src="../js/lazy-load-graphs.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,246 @@
|
||||
<!doctype html>
|
||||
<html class="default no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>ConnectionInterceptor | CICADA</title>
|
||||
<meta name="description" content="Documentation for CICADA">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
<script async src="../assets/js/search.js" id="search-script"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="tsd-page-toolbar">
|
||||
<div class="container">
|
||||
<div class="table-wrap">
|
||||
<div class="table-cell" id="tsd-search" data-index="../assets/js/search.json" data-base="..">
|
||||
<div class="field">
|
||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
||||
<input id="tsd-search-field" type="text" />
|
||||
</div>
|
||||
<ul class="results">
|
||||
<li class="state loading">Preparing search index...</li>
|
||||
<li class="state failure">The search index is not available</li>
|
||||
</ul>
|
||||
<a href="../index.html" class="title">CICADA</a>
|
||||
</div>
|
||||
<div class="table-cell" id="tsd-widgets">
|
||||
<div id="tsd-filter">
|
||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
||||
<div class="tsd-filter-group">
|
||||
<div class="tsd-select" id="tsd-filter-visibility">
|
||||
<span class="tsd-select-label">All</span>
|
||||
<ul class="tsd-select-list">
|
||||
<li data-value="public">Public</li>
|
||||
<li data-value="protected">Public/Protected</li>
|
||||
<li data-value="private" class="selected">All</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tsd-page-title">
|
||||
<div class="container">
|
||||
<ul class="tsd-breadcrumb">
|
||||
<li>
|
||||
<a href="../modules.html">CICADA</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="../modules/app__interceptors_connection_interceptor.html">app/_interceptors/connection.interceptor</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="app__interceptors_connection_interceptor.connectioninterceptor.html">ConnectionInterceptor</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>Class ConnectionInterceptor</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container container-main">
|
||||
<div class="row">
|
||||
<div class="col-8 col-content">
|
||||
<section class="tsd-panel tsd-comment">
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Intercepts and handles of events from outgoing HTTP request.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="tsd-panel tsd-hierarchy">
|
||||
<h3>Hierarchy</h3>
|
||||
<ul class="tsd-hierarchy">
|
||||
<li>
|
||||
<span class="target">ConnectionInterceptor</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="tsd-panel">
|
||||
<h3>Implements</h3>
|
||||
<ul class="tsd-hierarchy">
|
||||
<li><span class="tsd-signature-type">HttpInterceptor</span></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-index-group">
|
||||
<h2>Index</h2>
|
||||
<section class="tsd-panel tsd-index-panel">
|
||||
<div class="tsd-index-content">
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Constructors</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<li class="tsd-kind-constructor tsd-parent-kind-class"><a href="app__interceptors_connection_interceptor.connectioninterceptor.html#constructor" class="tsd-kind-icon">constructor</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Methods</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<li class="tsd-kind-method tsd-parent-kind-class"><a href="app__interceptors_connection_interceptor.connectioninterceptor.html#intercept" class="tsd-kind-icon">intercept</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-member-group ">
|
||||
<h2>Constructors</h2>
|
||||
<section class="tsd-panel tsd-member tsd-kind-constructor tsd-parent-kind-class">
|
||||
<a name="constructor" class="tsd-anchor"></a>
|
||||
<h3>constructor</h3>
|
||||
<ul class="tsd-signatures tsd-kind-constructor tsd-parent-kind-class">
|
||||
<li class="tsd-signature tsd-kind-icon">new <wbr>Connection<wbr>Interceptor<span class="tsd-signature-symbol">(</span>loggingService<span class="tsd-signature-symbol">: </span><a href="app__services_logging_service.loggingservice.html" class="tsd-signature-type" data-tsd-kind="Class">LoggingService</a><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><a href="app__interceptors_connection_interceptor.connectioninterceptor.html" class="tsd-signature-type" data-tsd-kind="Class">ConnectionInterceptor</a></li>
|
||||
</ul>
|
||||
<ul class="tsd-descriptions">
|
||||
<li class="tsd-description">
|
||||
<aside class="tsd-sources">
|
||||
<ul>
|
||||
<li>Defined in src/app/_interceptors/connection.interceptor.ts:14</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Initialization of the connection interceptor.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="tsd-parameters-title">Parameters</h4>
|
||||
<ul class="tsd-parameters">
|
||||
<li>
|
||||
<h5>loggingService: <a href="app__services_logging_service.loggingservice.html" class="tsd-signature-type" data-tsd-kind="Class">LoggingService</a></h5>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<p>A service that provides logging capabilities.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 class="tsd-returns-title">Returns <a href="app__interceptors_connection_interceptor.connectioninterceptor.html" class="tsd-signature-type" data-tsd-kind="Class">ConnectionInterceptor</a></h4>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-member-group ">
|
||||
<h2>Methods</h2>
|
||||
<section class="tsd-panel tsd-member tsd-kind-method tsd-parent-kind-class">
|
||||
<a name="intercept" class="tsd-anchor"></a>
|
||||
<h3>intercept</h3>
|
||||
<ul class="tsd-signatures tsd-kind-method tsd-parent-kind-class">
|
||||
<li class="tsd-signature tsd-kind-icon">intercept<span class="tsd-signature-symbol">(</span>request<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">HttpRequest</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">></span>, next<span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">HttpHandler</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">Observable</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">HttpEvent</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">></span><span class="tsd-signature-symbol">></span></li>
|
||||
</ul>
|
||||
<ul class="tsd-descriptions">
|
||||
<li class="tsd-description">
|
||||
<aside class="tsd-sources">
|
||||
<p>Implementation of HttpInterceptor.intercept</p>
|
||||
<ul>
|
||||
<li>Defined in src/app/_interceptors/connection.interceptor.ts:29</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<div class="lead">
|
||||
<p>Intercepts HTTP requests.</p>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="tsd-parameters-title">Parameters</h4>
|
||||
<ul class="tsd-parameters">
|
||||
<li>
|
||||
<h5>request: <span class="tsd-signature-type">HttpRequest</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">></span></h5>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<p>An outgoing HTTP request with an optional typed body.</p>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<h5>next: <span class="tsd-signature-type">HttpHandler</span></h5>
|
||||
<div class="tsd-comment tsd-typography">
|
||||
<p>The next HTTP handler or the outgoing request dispatcher.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Observable</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">HttpEvent</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">></span><span class="tsd-signature-symbol">></span></h4>
|
||||
<p>The forwarded request.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
||||
<nav class="tsd-navigation primary">
|
||||
<ul>
|
||||
<li class=" ">
|
||||
<a href="../modules.html">Exports</a>
|
||||
</li>
|
||||
<li class="current tsd-kind-module">
|
||||
<a href="../modules/app__interceptors_connection_interceptor.html">app/_<wbr>interceptors/connection.interceptor</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="tsd-navigation secondary menu-sticky">
|
||||
<ul class="before-current">
|
||||
</ul>
|
||||
<ul class="current">
|
||||
<li class="current tsd-kind-class tsd-parent-kind-module">
|
||||
<a href="app__interceptors_connection_interceptor.connectioninterceptor.html" class="tsd-kind-icon">Connection<wbr>Interceptor</a>
|
||||
<ul>
|
||||
<li class=" tsd-kind-constructor tsd-parent-kind-class">
|
||||
<a href="app__interceptors_connection_interceptor.connectioninterceptor.html#constructor" class="tsd-kind-icon">constructor</a>
|
||||
</li>
|
||||
<li class=" tsd-kind-method tsd-parent-kind-class">
|
||||
<a href="app__interceptors_connection_interceptor.connectioninterceptor.html#intercept" class="tsd-kind-icon">intercept</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="after-current">
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="with-border-bottom">
|
||||
<div class="container">
|
||||
<h2>Legend</h2>
|
||||
<div class="tsd-legend-group">
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
||||
<li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li>
|
||||
<li class="tsd-kind-method tsd-parent-kind-class"><span class="tsd-kind-icon">Method</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-variable"><span class="tsd-kind-icon">Variable</span></li>
|
||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</div>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
146
docs/typedoc/modules/app__helpers_online_status.html
Normal file
146
docs/typedoc/modules/app__helpers_online_status.html
Normal file
@@ -0,0 +1,146 @@
|
||||
<!doctype html>
|
||||
<html class="default no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>app/_helpers/online-status | CICADA</title>
|
||||
<meta name="description" content="Documentation for CICADA">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
<script async src="../assets/js/search.js" id="search-script"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="tsd-page-toolbar">
|
||||
<div class="container">
|
||||
<div class="table-wrap">
|
||||
<div class="table-cell" id="tsd-search" data-index="../assets/js/search.json" data-base="..">
|
||||
<div class="field">
|
||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
||||
<input id="tsd-search-field" type="text" />
|
||||
</div>
|
||||
<ul class="results">
|
||||
<li class="state loading">Preparing search index...</li>
|
||||
<li class="state failure">The search index is not available</li>
|
||||
</ul>
|
||||
<a href="../index.html" class="title">CICADA</a>
|
||||
</div>
|
||||
<div class="table-cell" id="tsd-widgets">
|
||||
<div id="tsd-filter">
|
||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
||||
<div class="tsd-filter-group">
|
||||
<div class="tsd-select" id="tsd-filter-visibility">
|
||||
<span class="tsd-select-label">All</span>
|
||||
<ul class="tsd-select-list">
|
||||
<li data-value="public">Public</li>
|
||||
<li data-value="protected">Public/Protected</li>
|
||||
<li data-value="private" class="selected">All</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tsd-page-title">
|
||||
<div class="container">
|
||||
<ul class="tsd-breadcrumb">
|
||||
<li>
|
||||
<a href="../modules.html">CICADA</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="app__helpers_online_status.html">app/_helpers/online-status</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>Module app/_helpers/online-status</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container container-main">
|
||||
<div class="row">
|
||||
<div class="col-8 col-content">
|
||||
<section class="tsd-panel-group tsd-index-group">
|
||||
<h2>Index</h2>
|
||||
<section class="tsd-panel tsd-index-panel">
|
||||
<div class="tsd-index-content">
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Functions</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<li class="tsd-kind-function tsd-parent-kind-module"><a href="app__helpers_online_status.html#checkonlinestatus" class="tsd-kind-icon">check<wbr>Online<wbr>Status</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section class="tsd-panel-group tsd-member-group ">
|
||||
<h2>Functions</h2>
|
||||
<section class="tsd-panel tsd-member tsd-kind-function tsd-parent-kind-module">
|
||||
<a name="checkonlinestatus" class="tsd-anchor"></a>
|
||||
<h3>check<wbr>Online<wbr>Status</h3>
|
||||
<ul class="tsd-signatures tsd-kind-function tsd-parent-kind-module">
|
||||
<li class="tsd-signature tsd-kind-icon">check<wbr>Online<wbr>Status<span class="tsd-signature-symbol">(</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">: </span><span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">></span></li>
|
||||
</ul>
|
||||
<ul class="tsd-descriptions">
|
||||
<li class="tsd-description">
|
||||
<aside class="tsd-sources">
|
||||
<ul>
|
||||
<li>Defined in src/app/_helpers/online-status.ts:6</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Promise</span><span class="tsd-signature-symbol"><</span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">></span></h4>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
||||
<nav class="tsd-navigation primary">
|
||||
<ul>
|
||||
<li class=" ">
|
||||
<a href="../modules.html">Exports</a>
|
||||
</li>
|
||||
<li class="current tsd-kind-module">
|
||||
<a href="app__helpers_online_status.html">app/_<wbr>helpers/online-<wbr>status</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="tsd-navigation secondary menu-sticky">
|
||||
<ul class="before-current">
|
||||
<li class=" tsd-kind-function tsd-parent-kind-module">
|
||||
<a href="app__helpers_online_status.html#checkonlinestatus" class="tsd-kind-icon">check<wbr>Online<wbr>Status</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="with-border-bottom">
|
||||
<div class="container">
|
||||
<h2>Legend</h2>
|
||||
<div class="tsd-legend-group">
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-variable"><span class="tsd-kind-icon">Variable</span></li>
|
||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</div>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,126 @@
|
||||
<!doctype html>
|
||||
<html class="default no-js">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>app/_interceptors/connection.interceptor | CICADA</title>
|
||||
<meta name="description" content="Documentation for CICADA">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="../assets/css/main.css">
|
||||
<script async src="../assets/js/search.js" id="search-script"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="tsd-page-toolbar">
|
||||
<div class="container">
|
||||
<div class="table-wrap">
|
||||
<div class="table-cell" id="tsd-search" data-index="../assets/js/search.json" data-base="..">
|
||||
<div class="field">
|
||||
<label for="tsd-search-field" class="tsd-widget search no-caption">Search</label>
|
||||
<input id="tsd-search-field" type="text" />
|
||||
</div>
|
||||
<ul class="results">
|
||||
<li class="state loading">Preparing search index...</li>
|
||||
<li class="state failure">The search index is not available</li>
|
||||
</ul>
|
||||
<a href="../index.html" class="title">CICADA</a>
|
||||
</div>
|
||||
<div class="table-cell" id="tsd-widgets">
|
||||
<div id="tsd-filter">
|
||||
<a href="#" class="tsd-widget options no-caption" data-toggle="options">Options</a>
|
||||
<div class="tsd-filter-group">
|
||||
<div class="tsd-select" id="tsd-filter-visibility">
|
||||
<span class="tsd-select-label">All</span>
|
||||
<ul class="tsd-select-list">
|
||||
<li data-value="public">Public</li>
|
||||
<li data-value="protected">Public/Protected</li>
|
||||
<li data-value="private" class="selected">All</li>
|
||||
</ul>
|
||||
</div>
|
||||
<input type="checkbox" id="tsd-filter-inherited" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-inherited">Inherited</label>
|
||||
<input type="checkbox" id="tsd-filter-externals" checked />
|
||||
<label class="tsd-widget" for="tsd-filter-externals">Externals</label>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="tsd-widget menu no-caption" data-toggle="menu">Menu</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tsd-page-title">
|
||||
<div class="container">
|
||||
<ul class="tsd-breadcrumb">
|
||||
<li>
|
||||
<a href="../modules.html">CICADA</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="app__interceptors_connection_interceptor.html">app/_interceptors/connection.interceptor</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h1>Module app/_interceptors/connection.interceptor</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container container-main">
|
||||
<div class="row">
|
||||
<div class="col-8 col-content">
|
||||
<section class="tsd-panel-group tsd-index-group">
|
||||
<h2>Index</h2>
|
||||
<section class="tsd-panel tsd-index-panel">
|
||||
<div class="tsd-index-content">
|
||||
<section class="tsd-index-section ">
|
||||
<h3>Classes</h3>
|
||||
<ul class="tsd-index-list">
|
||||
<li class="tsd-kind-class tsd-parent-kind-module"><a href="../classes/app__interceptors_connection_interceptor.connectioninterceptor.html" class="tsd-kind-icon">Connection<wbr>Interceptor</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-4 col-menu menu-sticky-wrap menu-highlight">
|
||||
<nav class="tsd-navigation primary">
|
||||
<ul>
|
||||
<li class=" ">
|
||||
<a href="../modules.html">Exports</a>
|
||||
</li>
|
||||
<li class="current tsd-kind-module">
|
||||
<a href="app__interceptors_connection_interceptor.html">app/_<wbr>interceptors/connection.interceptor</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav class="tsd-navigation secondary menu-sticky">
|
||||
<ul class="before-current">
|
||||
<li class=" tsd-kind-class tsd-parent-kind-module">
|
||||
<a href="../classes/app__interceptors_connection_interceptor.connectioninterceptor.html" class="tsd-kind-icon">Connection<wbr>Interceptor</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="with-border-bottom">
|
||||
<div class="container">
|
||||
<h2>Legend</h2>
|
||||
<div class="tsd-legend-group">
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-variable"><span class="tsd-kind-icon">Variable</span></li>
|
||||
<li class="tsd-kind-function"><span class="tsd-kind-icon">Function</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-interface"><span class="tsd-kind-icon">Interface</span></li>
|
||||
</ul>
|
||||
<ul class="tsd-legend">
|
||||
<li class="tsd-kind-class"><span class="tsd-kind-icon">Class</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<div class="container tsd-generator">
|
||||
<p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p>
|
||||
</div>
|
||||
<div class="overlay"></div>
|
||||
<script src="../assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
29002
package-lock.json
generated
29002
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -74,6 +74,7 @@
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"lint-staged": "^11.0.0",
|
||||
"openpgp": "^4.10.10",
|
||||
"prettier": "^2.3.1",
|
||||
"pretty-quick": "^3.1.0",
|
||||
"protractor": "~7.0.0",
|
||||
|
||||
@@ -9,3 +9,5 @@ export * from '@app/_helpers/mock-backend';
|
||||
export * from '@app/_helpers/read-csv';
|
||||
export * from '@app/_helpers/schema-validation';
|
||||
export * from '@app/_helpers/sync';
|
||||
export * from '@app/_helpers/online-status';
|
||||
export * from '@app/_helpers/to-hex';
|
||||
|
||||
15
src/app/_helpers/online-status.ts
Normal file
15
src/app/_helpers/online-status.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
const apiUrls = [
|
||||
'https://api.coindesk.com/v1/bpi/currentprice.json',
|
||||
'https://dog.ceo/api/breeds/image/random',
|
||||
];
|
||||
|
||||
async function checkOnlineStatus(): Promise<boolean> {
|
||||
try {
|
||||
const online = await fetch(apiUrls[Math.floor(Math.random() * apiUrls.length)]);
|
||||
return online.status >= 200 && online.status < 300;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export { checkOnlineStatus };
|
||||
9
src/app/_helpers/to-hex.ts
Normal file
9
src/app/_helpers/to-hex.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
function asciiToHex(str: string): string {
|
||||
const arr = [];
|
||||
for (let n = 0, l = str.length; n < l; n++) {
|
||||
arr.push(Number(str.charCodeAt(n)).toString(16));
|
||||
}
|
||||
return arr.join('');
|
||||
}
|
||||
|
||||
export { asciiToHex };
|
||||
16
src/app/_interceptors/connection.interceptor.spec.ts
Normal file
16
src/app/_interceptors/connection.interceptor.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConnectionInterceptor } from './connection.interceptor';
|
||||
|
||||
describe('ConnectionInterceptor', () => {
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
providers: [ConnectionInterceptor],
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const interceptor: ConnectionInterceptor = TestBed.inject(ConnectionInterceptor);
|
||||
expect(interceptor).toBeTruthy();
|
||||
});
|
||||
});
|
||||
42
src/app/_interceptors/connection.interceptor.ts
Normal file
42
src/app/_interceptors/connection.interceptor.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
// Core imports
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
// Third party imports
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// Application imports
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { checkOnlineStatus } from '@app/_helpers';
|
||||
|
||||
/** Intercepts and handles of events from outgoing HTTP request. */
|
||||
@Injectable()
|
||||
export class ConnectionInterceptor implements HttpInterceptor {
|
||||
/**
|
||||
* Initialization of the connection interceptor.
|
||||
*
|
||||
* @param loggingService - A service that provides logging capabilities.
|
||||
*/
|
||||
constructor(private loggingService: LoggingService) {}
|
||||
|
||||
/**
|
||||
* Intercepts HTTP requests.
|
||||
*
|
||||
* @param request - An outgoing HTTP request with an optional typed body.
|
||||
* @param next - The next HTTP handler or the outgoing request dispatcher.
|
||||
* @returns The forwarded request.
|
||||
*/
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
checkOnlineStatus().then((online) => {
|
||||
if (!online) {
|
||||
this.loggingService.sendErrorLevelMessage('No internet connection on device!', this, {
|
||||
error: `NetworkError when attempting to fetch resource ${request.url}.`,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
return next.handle(request);
|
||||
}
|
||||
});
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,10 @@ export class ErrorInterceptor implements HttpInterceptor {
|
||||
this.router.navigateByUrl('/auth').then();
|
||||
break;
|
||||
case 403: // forbidden
|
||||
alert('Access to resource is not allowed!');
|
||||
this.errorDialogService.openDialog({
|
||||
message: 'Access to resource is not allowed (Error 403)',
|
||||
});
|
||||
// alert('Access to resource is not allowed!');
|
||||
break;
|
||||
}
|
||||
// Return an observable with a user-facing error message.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from '@app/_interceptors/connection.interceptor';
|
||||
export * from '@app/_interceptors/error.interceptor';
|
||||
export * from '@app/_interceptors/http-config.interceptor';
|
||||
export * from '@app/_interceptors/logging.interceptor';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { environment } from '@src/environments/environment';
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { MutableKeyStore } from '@app/_pgp';
|
||||
import { ErrorDialogService } from '@app/_services/error-dialog.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpError, rejectBody } from '@app/_helpers/global-error-handler';
|
||||
import { Staff } from '@app/_models';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
@@ -23,7 +22,6 @@ export class AuthService {
|
||||
trustedUsersSubject: Observable<Array<Staff>> = this.trustedUsersList.asObservable();
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private loggingService: LoggingService,
|
||||
private errorDialogService: ErrorDialogService
|
||||
) {}
|
||||
@@ -48,8 +46,9 @@ export class AuthService {
|
||||
}
|
||||
|
||||
getWithToken(): Promise<boolean> {
|
||||
const sessionToken = this.getSessionToken();
|
||||
const headers = {
|
||||
Authorization: 'Bearer ' + this.getSessionToken,
|
||||
Authorization: 'Bearer ' + sessionToken,
|
||||
'Content-Type': 'application/json;charset=utf-8',
|
||||
'x-cic-automerge': 'none',
|
||||
};
|
||||
@@ -93,36 +92,35 @@ export class AuthService {
|
||||
async login(): Promise<boolean> {
|
||||
if (this.getSessionToken()) {
|
||||
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
|
||||
} else {
|
||||
const o = await this.getChallenge();
|
||||
|
||||
const r = await signChallenge(
|
||||
o.challenge,
|
||||
o.realm,
|
||||
environment.cicMetaUrl,
|
||||
this.mutableKeyStore
|
||||
);
|
||||
|
||||
const tokenResponse = await this.sendSignedChallenge(r).then((response) => {
|
||||
const token = response.headers.get('Token');
|
||||
if (token) {
|
||||
return token;
|
||||
}
|
||||
if (response.status === 401) {
|
||||
throw new HttpError('You are not authorized to use this system', response.status);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new HttpError('Unknown error from authentication server', response.status);
|
||||
}
|
||||
});
|
||||
|
||||
if (tokenResponse) {
|
||||
this.setSessionToken(tokenResponse);
|
||||
this.setState('Click button to log in');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const o = await this.getChallenge();
|
||||
|
||||
const r = await signChallenge(
|
||||
o.challenge,
|
||||
o.realm,
|
||||
environment.cicMetaUrl,
|
||||
this.mutableKeyStore
|
||||
);
|
||||
|
||||
const tokenResponse = await this.sendSignedChallenge(r).then((response) => {
|
||||
const token = response.headers.get('Token');
|
||||
if (token) {
|
||||
return token;
|
||||
}
|
||||
if (response.status === 401) {
|
||||
throw new HttpError('You are not authorized to use this system', response.status);
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new HttpError('Unknown error from authentication server', response.status);
|
||||
}
|
||||
});
|
||||
|
||||
if (tokenResponse) {
|
||||
this.setSessionToken(tokenResponse);
|
||||
// this.setState('Click button to log in');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
loginView(): void {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { TransactionHelper } from '@cicnet/cic-client';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { TransactionService } from '@app/_services/transaction.service';
|
||||
import { environment } from '@src/environments/environment';
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { RegistryService } from '@app/_services/registry.service';
|
||||
import { Web3Service } from '@app/_services/web3.service';
|
||||
|
||||
@@ -15,17 +14,9 @@ export class BlockSyncService {
|
||||
readyStateTarget: number = 2;
|
||||
readyState: number = 0;
|
||||
|
||||
constructor(
|
||||
private transactionService: TransactionService,
|
||||
private loggingService: LoggingService
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
await this.transactionService.init();
|
||||
}
|
||||
constructor(private transactionService: TransactionService) {}
|
||||
|
||||
async blockSync(address: string = null, offset: number = 0, limit: number = 100): Promise<void> {
|
||||
this.transactionService.resetTransactionsList();
|
||||
const settings: Settings = new Settings(this.scan);
|
||||
const readyStateElements: { network: number } = { network: 2 };
|
||||
settings.w3.provider = environment.web3Provider;
|
||||
|
||||
@@ -35,6 +35,7 @@ export class LocationService {
|
||||
return queriedAreaName;
|
||||
}
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
getAreaTypes(): void {
|
||||
@@ -54,5 +55,6 @@ export class LocationService {
|
||||
return queriedAreaType;
|
||||
}
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ import { NGXLogger } from 'ngx-logger';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LoggingService {
|
||||
env: string;
|
||||
canDebug: boolean;
|
||||
|
||||
constructor(private logger: NGXLogger) {
|
||||
// TRACE|DEBUG|INFO|LOG|WARN|ERROR|FATAL|OFF
|
||||
if (isDevMode()) {
|
||||
|
||||
@@ -22,9 +22,7 @@ export class TokenService {
|
||||
|
||||
async init(): Promise<void> {
|
||||
this.registry = await RegistryService.getRegistry();
|
||||
this.tokenRegistry = new TokenRegistry(
|
||||
await this.registry.getContractAddressByName('TokenRegistry')
|
||||
);
|
||||
this.tokenRegistry = await RegistryService.getTokenRegistry();
|
||||
this.load.next(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { add0x, fromHex, strip0x, toHex } from '@src/assets/js/ethtx/dist/hex';
|
||||
import { Tx } from '@src/assets/js/ethtx/dist';
|
||||
import { toValue } from '@src/assets/js/ethtx/dist/tx';
|
||||
import * as secp256k1 from 'secp256k1';
|
||||
import { AuthService } from '@app/_services/auth.service';
|
||||
import { defaultAccount } from '@app/_models';
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
@@ -28,13 +27,11 @@ export class TransactionService {
|
||||
transactions: any[] = [];
|
||||
private transactionList = new BehaviorSubject<any[]>(this.transactions);
|
||||
transactionsSubject = this.transactionList.asObservable();
|
||||
userInfo: any;
|
||||
web3: Web3;
|
||||
registry: CICRegistry;
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private authService: AuthService,
|
||||
private userService: UserService,
|
||||
private loggingService: LoggingService
|
||||
) {
|
||||
@@ -42,8 +39,6 @@ export class TransactionService {
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
await this.authService.init();
|
||||
await this.userService.init();
|
||||
this.registry = await RegistryService.getRegistry();
|
||||
}
|
||||
|
||||
@@ -181,7 +176,7 @@ export class TransactionService {
|
||||
const hash = hashFunction.digest();
|
||||
const methodSignature = hash.toString('hex').substring(0, 8);
|
||||
const abiCoder = new utils.AbiCoder();
|
||||
const abi = await abiCoder.encode(
|
||||
const abi = abiCoder.encode(
|
||||
['address', 'address', 'address', 'uint256'],
|
||||
[senderAddress, recipientAddress, tokenAddress, value]
|
||||
);
|
||||
|
||||
@@ -7,14 +7,13 @@ import { ArgPair, Envelope, Phone, Syncable, User } from 'cic-client-meta';
|
||||
import { AccountDetails } from '@app/_models';
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { TokenService } from '@app/_services/token.service';
|
||||
import { AccountIndex } from '@app/_eth';
|
||||
import { MutableKeyStore, PGPSigner, Signer } from '@app/_pgp';
|
||||
import { RegistryService } from '@app/_services/registry.service';
|
||||
import { CICRegistry } from '@cicnet/cic-client';
|
||||
import { AuthService } from '@app/_services/auth.service';
|
||||
import { personValidation, updateSyncable, vcardValidation } from '@app/_helpers';
|
||||
import { add0x } from '@src/assets/js/ethtx/dist/hex';
|
||||
import { add0x, strip0x } from '@src/assets/js/ethtx/dist/hex';
|
||||
import { KeystoreService } from '@app/_services/keystore.service';
|
||||
import * as Automerge from 'automerge';
|
||||
const vCard = require('vcard-parser');
|
||||
|
||||
@Injectable({
|
||||
@@ -40,16 +39,17 @@ export class UserService {
|
||||
private categoriesList: BehaviorSubject<object> = new BehaviorSubject<object>(this.categories);
|
||||
categoriesSubject: Observable<object> = this.categoriesList.asObservable();
|
||||
|
||||
history: Array<any> = [];
|
||||
private historyList: BehaviorSubject<any> = new BehaviorSubject<any>(this.history);
|
||||
historySubject: Observable<Array<any>> = this.historyList.asObservable();
|
||||
|
||||
constructor(
|
||||
private httpClient: HttpClient,
|
||||
private loggingService: LoggingService,
|
||||
private tokenService: TokenService,
|
||||
private authService: AuthService
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
async init(): Promise<void> {
|
||||
await this.authService.init();
|
||||
await this.tokenService.init();
|
||||
this.keystore = await KeystoreService.getKeystore();
|
||||
this.signer = new PGPSigner(this.keystore);
|
||||
this.registry = await RegistryService.getRegistry();
|
||||
@@ -80,49 +80,21 @@ export class UserService {
|
||||
businessCategory: string,
|
||||
userLocation: string,
|
||||
location: string,
|
||||
locationType: string
|
||||
locationType: string,
|
||||
oldPhoneNumber: string
|
||||
): Promise<any> {
|
||||
const accountInfo: any = {
|
||||
vcard: {
|
||||
fn: [{}],
|
||||
n: [{}],
|
||||
tel: [{}],
|
||||
},
|
||||
location: {},
|
||||
};
|
||||
if (name) {
|
||||
accountInfo.vcard.fn[0].value = name;
|
||||
accountInfo.vcard.n[0].value = name.split(' ');
|
||||
}
|
||||
if (phoneNumber) {
|
||||
accountInfo.vcard.tel[0].value = phoneNumber;
|
||||
}
|
||||
if (bio) {
|
||||
accountInfo.products = [bio];
|
||||
}
|
||||
if (gender) {
|
||||
accountInfo.gender = gender;
|
||||
}
|
||||
if (age) {
|
||||
accountInfo.age = age;
|
||||
}
|
||||
if (type) {
|
||||
accountInfo.type = type;
|
||||
}
|
||||
if (businessCategory) {
|
||||
accountInfo.category = businessCategory;
|
||||
}
|
||||
if (location) {
|
||||
accountInfo.location.area = location;
|
||||
}
|
||||
if (userLocation) {
|
||||
accountInfo.location.area_name = userLocation;
|
||||
}
|
||||
if (locationType) {
|
||||
accountInfo.location.area_type = locationType;
|
||||
}
|
||||
await vcardValidation(accountInfo.vcard);
|
||||
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
|
||||
const accountInfo = await this.loadChangesToAccountStructure(
|
||||
name,
|
||||
phoneNumber,
|
||||
age,
|
||||
type,
|
||||
bio,
|
||||
gender,
|
||||
businessCategory,
|
||||
userLocation,
|
||||
location,
|
||||
locationType
|
||||
);
|
||||
const accountKey: string = await User.toKey(address);
|
||||
this.getAccountDetailsFromMeta(accountKey)
|
||||
.pipe(first())
|
||||
@@ -147,6 +119,14 @@ export class UserService {
|
||||
await this.updateMeta(syncableAccount, accountKey, this.headers);
|
||||
}
|
||||
);
|
||||
if (phoneNumber !== oldPhoneNumber) {
|
||||
const oldPhoneKey: string = await Phone.toKey(oldPhoneNumber);
|
||||
const newPhoneKey: string = await Phone.toKey(phoneNumber);
|
||||
const newPhoneData: Syncable = new Syncable(newPhoneKey, strip0x(address));
|
||||
await this.updateMeta(newPhoneData, newPhoneKey, this.headers);
|
||||
const oldPhoneData: Syncable = new Syncable(oldPhoneKey, '');
|
||||
await this.updateMeta(oldPhoneData, oldPhoneKey, this.headers);
|
||||
}
|
||||
return accountKey;
|
||||
}
|
||||
|
||||
@@ -203,18 +183,36 @@ export class UserService {
|
||||
}
|
||||
|
||||
async loadAccounts(limit: number = 100, offset: number = 0): Promise<void> {
|
||||
this.resetAccountsList();
|
||||
// const accountIndexAddress: string = await this.registry.getContractAddressByName(
|
||||
// 'AccountRegistry'
|
||||
// );
|
||||
// const accountIndexQuery = new AccountIndex(accountIndexAddress);
|
||||
// const accountAddresses: Array<string> = await accountIndexQuery.last(limit);
|
||||
try {
|
||||
const accountRegistry = await RegistryService.getAccountRegistry();
|
||||
const accountAddresses: Array<string> = await accountRegistry.last(limit);
|
||||
const accountAddresses: Array<string> = await accountRegistry.last(offset + limit);
|
||||
this.loggingService.sendInfoLevelMessage(accountAddresses);
|
||||
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
||||
await this.getAccountByAddress(accountAddress, limit);
|
||||
if (typeof Worker !== 'undefined') {
|
||||
const worker = new Worker('@app/_workers/fetch-accounts.worker', { type: 'module' });
|
||||
worker.onmessage = ({ data }) => {
|
||||
if (data) {
|
||||
this.tokenService.load.subscribe(async (status: boolean) => {
|
||||
if (status) {
|
||||
data.balance = await this.tokenService.getTokenBalance(
|
||||
data.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0]
|
||||
);
|
||||
}
|
||||
});
|
||||
this.addAccount(data, limit);
|
||||
}
|
||||
};
|
||||
worker.postMessage({
|
||||
addresses: accountAddresses.slice(offset, offset + limit),
|
||||
url: environment.cicMetaUrl,
|
||||
token: sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN')),
|
||||
});
|
||||
} else {
|
||||
this.loggingService.sendInfoLevelMessage(
|
||||
'Web workers are not supported in this environment'
|
||||
);
|
||||
for (const accountAddress of accountAddresses.slice(offset, offset + limit)) {
|
||||
await this.getAccountByAddress(accountAddress, limit);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.loggingService.sendErrorLevelMessage('Unable to load accounts.', 'user.service', error);
|
||||
@@ -224,13 +222,22 @@ export class UserService {
|
||||
|
||||
async getAccountByAddress(
|
||||
accountAddress: string,
|
||||
limit: number = 100
|
||||
limit: number = 100,
|
||||
history: boolean = false
|
||||
): Promise<Observable<AccountDetails>> {
|
||||
const accountSubject: Subject<any> = new Subject<any>();
|
||||
this.getAccountDetailsFromMeta(await User.toKey(add0x(accountAddress)))
|
||||
.pipe(first())
|
||||
.subscribe(async (res) => {
|
||||
const account: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
if (history) {
|
||||
try {
|
||||
// @ts-ignore
|
||||
this.historyList.next(Automerge.getHistory(account.m).reverse());
|
||||
} catch (error) {
|
||||
this.loggingService.sendErrorLevelMessage('No history found', this, { error });
|
||||
}
|
||||
}
|
||||
const accountInfo = account.m.data;
|
||||
await personValidation(accountInfo);
|
||||
this.tokenService.load.subscribe(async (status: boolean) => {
|
||||
@@ -271,10 +278,6 @@ export class UserService {
|
||||
this.accountsList.next(this.accounts);
|
||||
}
|
||||
|
||||
searchAccountByName(name: string): any {
|
||||
return;
|
||||
}
|
||||
|
||||
getCategories(): void {
|
||||
this.httpClient
|
||||
.get(`${environment.cicMetaUrl}/categories`)
|
||||
@@ -292,6 +295,7 @@ export class UserService {
|
||||
return queriedCategory;
|
||||
}
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
getAccountTypes(): Observable<any> {
|
||||
@@ -324,4 +328,60 @@ export class UserService {
|
||||
}
|
||||
this.accountsList.next(this.accounts);
|
||||
}
|
||||
|
||||
async loadChangesToAccountStructure(
|
||||
name: string,
|
||||
phoneNumber: string,
|
||||
age: string,
|
||||
type: string,
|
||||
bio: string,
|
||||
gender: string,
|
||||
businessCategory: string,
|
||||
userLocation: string,
|
||||
location: string,
|
||||
locationType: string
|
||||
): Promise<AccountDetails> {
|
||||
const accountInfo: any = {
|
||||
vcard: {
|
||||
fn: [{}],
|
||||
n: [{}],
|
||||
tel: [{}],
|
||||
},
|
||||
location: {},
|
||||
};
|
||||
if (name) {
|
||||
accountInfo.vcard.fn[0].value = name;
|
||||
accountInfo.vcard.n[0].value = name.split(' ');
|
||||
}
|
||||
if (phoneNumber) {
|
||||
accountInfo.vcard.tel[0].value = phoneNumber;
|
||||
}
|
||||
if (bio) {
|
||||
accountInfo.products = [bio];
|
||||
}
|
||||
if (gender) {
|
||||
accountInfo.gender = gender;
|
||||
}
|
||||
if (age) {
|
||||
accountInfo.age = age;
|
||||
}
|
||||
if (type) {
|
||||
accountInfo.type = type;
|
||||
}
|
||||
if (businessCategory) {
|
||||
accountInfo.category = businessCategory;
|
||||
}
|
||||
if (location) {
|
||||
accountInfo.location.area = location;
|
||||
}
|
||||
if (userLocation) {
|
||||
accountInfo.location.area_name = userLocation;
|
||||
}
|
||||
if (locationType) {
|
||||
accountInfo.location.area_type = locationType;
|
||||
}
|
||||
await vcardValidation(accountInfo.vcard);
|
||||
accountInfo.vcard = btoa(vCard.generate(accountInfo.vcard));
|
||||
return accountInfo;
|
||||
}
|
||||
}
|
||||
|
||||
57
src/app/_workers/fetch-accounts.worker.ts
Normal file
57
src/app/_workers/fetch-accounts.worker.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
import { Envelope, Syncable, User } from 'cic-client-meta';
|
||||
import { add0x } from '@src/assets/js/ethtx/dist/hex';
|
||||
import { personValidation, vcardValidation } from '@app/_helpers/schema-validation';
|
||||
import * as vCard from 'vcard-parser';
|
||||
|
||||
const headers = {
|
||||
'x-cic-automerge': 'client',
|
||||
};
|
||||
|
||||
const options = {
|
||||
headers,
|
||||
};
|
||||
|
||||
addEventListener('message', async ({ data }) => {
|
||||
if (data.addresses instanceof Array) {
|
||||
for (const accountAddress of data.addresses) {
|
||||
try {
|
||||
const account = await getAccountByAddress(accountAddress, data.url, data.token);
|
||||
postMessage(account);
|
||||
} catch (error) {
|
||||
console.log(`ERROR we failed to get account ${accountAddress}`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function getAccountByAddress(
|
||||
accountAddress: string,
|
||||
metaUrl: string,
|
||||
token: string
|
||||
): Promise<any> {
|
||||
const userKey = await User.toKey(add0x(accountAddress));
|
||||
|
||||
headers['Authorization'] = 'Bearer ' + token;
|
||||
const response = await fetch(`${metaUrl}/${userKey}`, options)
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
} else {
|
||||
return Promise.reject({
|
||||
status: res.status,
|
||||
statusText: res.statusText,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
throw Error(`${error.status}: ${error.statusText}`);
|
||||
});
|
||||
const account: Syncable = Envelope.fromJSON(JSON.stringify(response)).unwrap();
|
||||
const accountInfo = account.m.data;
|
||||
await personValidation(accountInfo);
|
||||
accountInfo.vcard = vCard.parse(atob(accountInfo.vcard));
|
||||
await vcardValidation(accountInfo.vcard);
|
||||
return accountInfo;
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
import { ChangeDetectionStrategy, Component, HostListener, OnInit } from '@angular/core';
|
||||
import {
|
||||
AuthService,
|
||||
BlockSyncService,
|
||||
ErrorDialogService,
|
||||
LoggingService,
|
||||
TokenService,
|
||||
TransactionService,
|
||||
UserService,
|
||||
} from '@app/_services';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
@@ -16,16 +20,20 @@ import { SwUpdate } from '@angular/service-worker';
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
title = 'CICADA';
|
||||
readyStateTarget: number = 3;
|
||||
readyState: number = 0;
|
||||
mediaQuery: MediaQueryList = window.matchMedia('(max-width: 768px)');
|
||||
url: string;
|
||||
accountDetailsRegex = '/accounts/[a-z,A-Z,0-9]{40}';
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private transactionService: TransactionService,
|
||||
private loggingService: LoggingService,
|
||||
private blockSyncService: BlockSyncService,
|
||||
private errorDialogService: ErrorDialogService,
|
||||
private swUpdate: SwUpdate
|
||||
private loggingService: LoggingService,
|
||||
private tokenService: TokenService,
|
||||
private transactionService: TransactionService,
|
||||
private userService: UserService,
|
||||
private swUpdate: SwUpdate,
|
||||
private router: Router
|
||||
) {
|
||||
this.mediaQuery.addEventListener('change', this.onResize);
|
||||
this.onResize(this.mediaQuery);
|
||||
@@ -33,6 +41,8 @@ export class AppComponent implements OnInit {
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.authService.init();
|
||||
await this.tokenService.init();
|
||||
await this.userService.init();
|
||||
await this.transactionService.init();
|
||||
try {
|
||||
const publicKeys = await this.authService.getPublicKeys();
|
||||
@@ -51,6 +61,7 @@ export class AppComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
await this.routeManagement();
|
||||
}
|
||||
|
||||
// Load resize
|
||||
@@ -89,4 +100,31 @@ export class AppComponent implements OnInit {
|
||||
const conversion: any = event.detail.tx;
|
||||
await this.transactionService.setConversion(conversion, 100);
|
||||
}
|
||||
|
||||
async routeManagement(): Promise<void> {
|
||||
await this.router.events
|
||||
.pipe(filter((e) => e instanceof NavigationEnd))
|
||||
.forEach(async (routeInfo) => {
|
||||
if (routeInfo instanceof NavigationEnd) {
|
||||
this.url = routeInfo.url;
|
||||
if (!this.url.match(this.accountDetailsRegex) || !this.url.includes('tx')) {
|
||||
await this.blockSyncService.blockSync();
|
||||
}
|
||||
if (!this.url.includes('accounts')) {
|
||||
try {
|
||||
await this.userService.loadAccounts(100);
|
||||
} catch (error) {
|
||||
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error });
|
||||
}
|
||||
}
|
||||
if (!this.url.includes('tokens')) {
|
||||
this.tokenService.load.subscribe(async (status: boolean) => {
|
||||
if (status) {
|
||||
await this.tokenService.getTokens();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,12 @@ import { MatTableModule } from '@angular/material/table';
|
||||
import { AuthGuard } from '@app/_guards';
|
||||
import { LoggerModule } from 'ngx-logger';
|
||||
import { environment } from '@src/environments/environment';
|
||||
import { ErrorInterceptor, HttpConfigInterceptor, LoggingInterceptor } from '@app/_interceptors';
|
||||
import {
|
||||
ConnectionInterceptor,
|
||||
ErrorInterceptor,
|
||||
HttpConfigInterceptor,
|
||||
LoggingInterceptor,
|
||||
} from '@app/_interceptors';
|
||||
import { MutablePgpKeyStore } from '@app/_pgp';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
@@ -38,6 +43,7 @@ import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
MockBackendProvider,
|
||||
GlobalErrorHandler,
|
||||
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: ConnectionInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: HttpConfigInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
|
||||
|
||||
@@ -3,7 +3,6 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { CustomErrorStateMatcher } from '@app/_helpers';
|
||||
import { AuthService } from '@app/_services';
|
||||
import { ErrorDialogService } from '@app/_services/error-dialog.service';
|
||||
import { LoggingService } from '@app/_services/logging.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
@@ -25,7 +24,7 @@ export class AuthComponent implements OnInit {
|
||||
private errorDialogService: ErrorDialogService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
ngOnInit(): void {
|
||||
this.keyForm = this.formBuilder.group({
|
||||
key: ['', Validators.required],
|
||||
});
|
||||
@@ -58,7 +57,7 @@ export class AuthComponent implements OnInit {
|
||||
}
|
||||
} catch (HttpError) {
|
||||
this.errorDialogService.openDialog({
|
||||
message: HttpError.message,
|
||||
message: 'Failed to login please try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,88 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab-group *ngIf="account" dynamicHeight mat-align-tabs="start">
|
||||
<div class="card mt-1">
|
||||
<app-account-history
|
||||
*ngIf="history"
|
||||
[account]="history?.snapshot.data"
|
||||
(closeWindow)="history = $event"
|
||||
></app-account-history>
|
||||
<mat-card-title class="card-header"> HISTORY </mat-card-title>
|
||||
<div class="card-body">
|
||||
<div *ngIf="historyLoading">
|
||||
<h2 class="text-center"><strong>Loading History!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
class="mat-elevation-z10"
|
||||
[dataSource]="historyDataSource"
|
||||
matSort
|
||||
matSortActive="timestamp"
|
||||
#HistoryTableSort="matSort"
|
||||
matSortDirection="asc"
|
||||
matSortDisableClear
|
||||
>
|
||||
<ng-container matColumnDef="actor">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Actor</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ history?.change.actor }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="signer">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Signer</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ history?.snapshot.signature?.data | signatureUser | async }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="message">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Message</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ history?.change.message }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="sequence">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Sequence</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ history?.change.seq }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="dependencies">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Dependencies</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ getKeyValue(history?.change.deps) }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="timestamp">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>ChangedAt</mat-header-cell>
|
||||
<mat-cell *matCellDef="let history">
|
||||
{{ history?.snapshot.timestamp | unixDate }}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="historyDisplayedColumns"></mat-header-row>
|
||||
<mat-row
|
||||
*matRowDef="let history; columns: historyDisplayedColumns"
|
||||
(click)="viewHistory(history)"
|
||||
matRipple
|
||||
></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator
|
||||
#HistoryTablePaginator="matPaginator"
|
||||
[pageSize]="historyDefaultPageSize"
|
||||
[pageSizeOptions]="historyPageSizeOptions"
|
||||
showFirstLastButtons
|
||||
></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab-group dynamicHeight mat-align-tabs="start">
|
||||
<mat-tab label="Transactions">
|
||||
<app-transaction-details
|
||||
[transaction]="transaction"
|
||||
@@ -382,6 +463,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="transactionsLoading">
|
||||
<h2 class="text-center"><strong>Loading Transactions!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<table
|
||||
mat-table
|
||||
class="mat-elevation-z10"
|
||||
@@ -491,6 +577,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="accountsLoading">
|
||||
<h2 class="text-center"><strong>Loading Accounts!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
class="mat-elevation-z10"
|
||||
[dataSource]="userDataSource"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
@@ -31,7 +32,7 @@ import { AccountDetails, Transaction } from '@app/_models';
|
||||
styleUrls: ['./account-details.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccountDetailsComponent implements OnInit {
|
||||
export class AccountDetailsComponent implements OnInit, AfterViewInit {
|
||||
transactionsDataSource: MatTableDataSource<any>;
|
||||
transactionsDisplayedColumns: Array<string> = ['sender', 'recipient', 'value', 'created', 'type'];
|
||||
transactionsDefaultPageSize: number = 10;
|
||||
@@ -46,6 +47,20 @@ export class AccountDetailsComponent implements OnInit {
|
||||
@ViewChild('UserTablePaginator', { static: true }) userTablePaginator: MatPaginator;
|
||||
@ViewChild('UserTableSort', { static: true }) userTableSort: MatSort;
|
||||
|
||||
historyDataSource: MatTableDataSource<any>;
|
||||
historyDisplayedColumns: Array<string> = [
|
||||
'actor',
|
||||
'signer',
|
||||
'message',
|
||||
'sequence',
|
||||
'dependencies',
|
||||
'timestamp',
|
||||
];
|
||||
historyDefaultPageSize: number = 10;
|
||||
historyPageSizeOptions: Array<number> = [10, 20, 50, 100];
|
||||
@ViewChild('HistoryTablePaginator', { static: true }) historyTablePaginator: MatPaginator;
|
||||
@ViewChild('HistoryTableSort', { static: true }) historyTableSort: MatSort;
|
||||
|
||||
accountInfoForm: FormGroup;
|
||||
account: AccountDetails;
|
||||
accountAddress: string;
|
||||
@@ -68,6 +83,11 @@ export class AccountDetailsComponent implements OnInit {
|
||||
category: string;
|
||||
area: string;
|
||||
areaType: string;
|
||||
accountsLoading: boolean = true;
|
||||
transactionsLoading: boolean = true;
|
||||
histories: Array<any> = [];
|
||||
history: any;
|
||||
historyLoading: boolean = true;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
@@ -90,60 +110,17 @@ export class AccountDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.accountInfoForm = this.formBuilder.group({
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required],
|
||||
phoneNumber: ['', Validators.required],
|
||||
age: [''],
|
||||
type: ['', Validators.required],
|
||||
bio: ['', Validators.required],
|
||||
gender: ['', Validators.required],
|
||||
businessCategory: ['', Validators.required],
|
||||
userLocation: ['', Validators.required],
|
||||
location: ['', Validators.required],
|
||||
locationType: ['', Validators.required],
|
||||
});
|
||||
await this.blockSyncService.init();
|
||||
await this.tokenService.init();
|
||||
await this.transactionService.init();
|
||||
await this.userService.init();
|
||||
this.buildAccountsInfoForm();
|
||||
this.transactionService.resetTransactionsList();
|
||||
await this.blockSyncService.blockSync(this.accountAddress);
|
||||
this.userService.resetAccountsList();
|
||||
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
|
||||
(await this.userService.getAccountByAddress(this.accountAddress, 100, true)).subscribe(
|
||||
async (res) => {
|
||||
if (res !== undefined) {
|
||||
this.account = res;
|
||||
this.cdr.detectChanges();
|
||||
this.loggingService.sendInfoLevelMessage(this.account);
|
||||
this.locationService.areaNamesSubject.subscribe((response) => {
|
||||
this.area = this.locationService.getAreaNameByLocation(
|
||||
this.account.location.area_name,
|
||||
response
|
||||
);
|
||||
this.cdr.detectChanges();
|
||||
this.locationService.areaTypesSubject.subscribe((result) => {
|
||||
this.areaType = this.locationService.getAreaTypeByArea(this.area, result);
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
});
|
||||
this.userService.categoriesSubject.subscribe((result) => {
|
||||
this.category = this.userService.getCategoryByProduct(this.account.products[0], result);
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
const fullName = this.account.vcard?.fn[0].value.split(' ');
|
||||
this.accountInfoForm.patchValue({
|
||||
firstName: fullName[0].split(',')[0],
|
||||
lastName: fullName.slice(1).join(' '),
|
||||
phoneNumber: this.account.vcard?.tel[0].value,
|
||||
age: this.account.age,
|
||||
type: this.account.type,
|
||||
bio: this.account.products,
|
||||
gender: this.account.gender,
|
||||
businessCategory: this.account.category || this.category || 'other',
|
||||
userLocation: this.account.location.area_name,
|
||||
location: this.account.location.area || this.area || 'other',
|
||||
locationType: this.account.location.area_type || this.areaType || 'other',
|
||||
});
|
||||
this.queryLocationAndCategory(this.account);
|
||||
this.populateAccountsInfoForm(this.account);
|
||||
this.userService
|
||||
.getAccountStatus(this.account.vcard?.tel[0].value)
|
||||
.pipe(first())
|
||||
@@ -153,45 +130,8 @@ export class AccountDetailsComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
);
|
||||
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.getCategories();
|
||||
this.userService.categoriesSubject.subscribe((res) => {
|
||||
this.categories = Object.keys(res);
|
||||
});
|
||||
this.locationService.getAreaNames();
|
||||
this.locationService.areaNamesSubject.subscribe((res) => {
|
||||
this.areaNames = Object.keys(res);
|
||||
});
|
||||
this.locationService.getAreaTypes();
|
||||
this.locationService.areaTypesSubject.subscribe((res) => {
|
||||
this.areaTypes = Object.keys(res);
|
||||
});
|
||||
this.userService
|
||||
.getAccountTypes()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.accountTypes = res));
|
||||
this.userService
|
||||
.getTransactionTypes()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.transactionsTypes = res));
|
||||
this.userService
|
||||
.getGenders()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.genders = res));
|
||||
this.populateDataTables();
|
||||
this.loadSearchData();
|
||||
this.tokenService.load.subscribe(async (status: boolean) => {
|
||||
if (status) {
|
||||
this.tokenSymbol = await this.tokenService.getTokenSymbol();
|
||||
@@ -199,6 +139,21 @@ export class AccountDetailsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.userDataSource) {
|
||||
this.userDataSource.paginator = this.userTablePaginator;
|
||||
this.userDataSource.sort = this.userTableSort;
|
||||
}
|
||||
if (this.transactionsDataSource) {
|
||||
this.transactionsDataSource.paginator = this.transactionTablePaginator;
|
||||
this.transactionsDataSource.sort = this.transactionTableSort;
|
||||
}
|
||||
if (this.historyDataSource) {
|
||||
this.historyDataSource.paginator = this.historyTablePaginator;
|
||||
this.historyDataSource.sort = this.historyTableSort;
|
||||
}
|
||||
}
|
||||
|
||||
doTransactionFilter(value: string): void {
|
||||
this.transactionsDataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
@@ -211,6 +166,10 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
viewHistory(history): void {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
viewAccount(account): void {
|
||||
this.router.navigateByUrl(
|
||||
`/accounts/${strip0x(account.identities.evm[`bloxberg:${environment.bloxbergChainId}`][0])}`
|
||||
@@ -237,7 +196,8 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.accountInfoFormStub.businessCategory.value,
|
||||
this.accountInfoFormStub.userLocation.value,
|
||||
this.accountInfoFormStub.location.value,
|
||||
this.accountInfoFormStub.locationType.value
|
||||
this.accountInfoFormStub.locationType.value,
|
||||
this.account.vcard?.tel[0].value
|
||||
);
|
||||
this.submitted = false;
|
||||
}
|
||||
@@ -291,4 +251,127 @@ export class AccountDetailsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getKeyValue(obj: any): string {
|
||||
let str = '';
|
||||
if (obj instanceof Object) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
str += `${key}: ${value} `;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
buildAccountsInfoForm(): void {
|
||||
this.accountInfoForm = this.formBuilder.group({
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required],
|
||||
phoneNumber: ['', Validators.required],
|
||||
age: [''],
|
||||
type: ['', Validators.required],
|
||||
bio: ['', Validators.required],
|
||||
gender: ['', Validators.required],
|
||||
businessCategory: ['', Validators.required],
|
||||
userLocation: ['', Validators.required],
|
||||
location: ['', Validators.required],
|
||||
locationType: ['', Validators.required],
|
||||
});
|
||||
}
|
||||
|
||||
populateAccountsInfoForm(accountInfo: AccountDetails): void {
|
||||
const fullName = accountInfo.vcard?.fn[0].value.split(' ');
|
||||
this.accountInfoForm.patchValue({
|
||||
firstName: fullName[0].split(',')[0],
|
||||
lastName: fullName.slice(1).join(' '),
|
||||
phoneNumber: accountInfo.vcard?.tel[0].value,
|
||||
age: accountInfo.age,
|
||||
type: accountInfo.type,
|
||||
bio: accountInfo.products,
|
||||
gender: accountInfo.gender,
|
||||
businessCategory: accountInfo.category || this.category || 'other',
|
||||
userLocation: accountInfo.location.area_name,
|
||||
location: accountInfo.location.area || this.area || 'other',
|
||||
locationType: accountInfo.location.area_type || this.areaType || 'other',
|
||||
});
|
||||
}
|
||||
|
||||
populateDataTables(): 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;
|
||||
if (accounts.length > 0) {
|
||||
this.accountsLoading = false;
|
||||
}
|
||||
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;
|
||||
if (transactions.length > 0) {
|
||||
this.transactionsLoading = false;
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
|
||||
this.userService.historySubject.subscribe(async (histories) => {
|
||||
this.historyDataSource = new MatTableDataSource<any>(histories);
|
||||
this.historyDataSource.paginator = this.historyTablePaginator;
|
||||
this.historyDataSource.sort = this.historyTableSort;
|
||||
this.histories = histories;
|
||||
if (histories.length > 0) {
|
||||
this.historyLoading = false;
|
||||
}
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
queryLocationAndCategory(accountInfo: AccountDetails): void {
|
||||
this.locationService.areaNamesSubject.subscribe((response) => {
|
||||
this.area = this.locationService.getAreaNameByLocation(
|
||||
accountInfo.location.area_name,
|
||||
response
|
||||
);
|
||||
this.cdr.detectChanges();
|
||||
this.locationService.areaTypesSubject.subscribe((result) => {
|
||||
this.areaType = this.locationService.getAreaTypeByArea(this.area, result);
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
});
|
||||
this.userService.categoriesSubject.subscribe((result) => {
|
||||
this.category = this.userService.getCategoryByProduct(accountInfo.products[0], result);
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
loadSearchData(): void {
|
||||
this.userService.getCategories();
|
||||
this.userService.categoriesSubject.subscribe((res) => {
|
||||
this.categories = Object.keys(res);
|
||||
});
|
||||
this.locationService.getAreaNames();
|
||||
this.locationService.areaNamesSubject.subscribe((res) => {
|
||||
this.areaNames = Object.keys(res);
|
||||
});
|
||||
this.locationService.getAreaTypes();
|
||||
this.locationService.areaTypesSubject.subscribe((res) => {
|
||||
this.areaTypes = Object.keys(res);
|
||||
});
|
||||
this.userService
|
||||
.getAccountTypes()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.accountTypes = res));
|
||||
this.userService
|
||||
.getTransactionTypes()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.transactionsTypes = res));
|
||||
this.userService
|
||||
.getGenders()
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.genders = res));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<div *ngIf="account" class="mb-3 mt-1">
|
||||
<div class="card text-center">
|
||||
<mat-card-title class="card-header">
|
||||
<div class="row">
|
||||
ACCOUNT DETAILS
|
||||
<button
|
||||
mat-raised-button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary ml-auto mr-2"
|
||||
(click)="close()"
|
||||
>
|
||||
CLOSE
|
||||
</button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<span>Name: {{ account?.vcard?.fn[0].value }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Phone Number: {{ account?.vcard?.tel[0].value }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Account Type: {{ account?.type }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Gender: {{ account?.gender }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Age: {{ account?.age }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<span>Bio: {{ account?.products }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Business Category: {{ account?.category }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>User Location: {{ account?.location?.area_name }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Location: {{ account?.location?.area }}</span>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>Location Type: {{ account?.location?.area_type }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AccountHistoryComponent } from './account-history.component';
|
||||
|
||||
describe('AccountHistoryComponent', () => {
|
||||
let component: AccountHistoryComponent;
|
||||
let fixture: ComponentFixture<AccountHistoryComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [AccountHistoryComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AccountHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
SimpleChanges,
|
||||
OnChanges,
|
||||
} from '@angular/core';
|
||||
const vCard = require('vcard-parser');
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-history',
|
||||
templateUrl: './account-history.component.html',
|
||||
styleUrls: ['./account-history.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccountHistoryComponent implements OnInit, OnChanges {
|
||||
@Input() account;
|
||||
|
||||
@Output() closeWindow: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this.account) {
|
||||
this.account.vcard = vCard.parse(atob(this.account.vcard));
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.account = null;
|
||||
this.closeWindow.emit(this.account);
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,6 @@ import { environment } from '@src/environments/environment';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccountSearchComponent implements OnInit {
|
||||
nameSearchForm: FormGroup;
|
||||
nameSearchSubmitted: boolean = false;
|
||||
nameSearchLoading: boolean = false;
|
||||
phoneSearchForm: FormGroup;
|
||||
phoneSearchSubmitted: boolean = false;
|
||||
phoneSearchLoading: boolean = false;
|
||||
@@ -29,9 +26,6 @@ export class AccountSearchComponent implements OnInit {
|
||||
private userService: UserService,
|
||||
private router: Router
|
||||
) {
|
||||
this.nameSearchForm = this.formBuilder.group({
|
||||
name: ['', Validators.required],
|
||||
});
|
||||
this.phoneSearchForm = this.formBuilder.group({
|
||||
phoneNumber: ['', Validators.required],
|
||||
});
|
||||
@@ -40,13 +34,8 @@ export class AccountSearchComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.userService.init();
|
||||
}
|
||||
ngOnInit(): void {}
|
||||
|
||||
get nameSearchFormStub(): any {
|
||||
return this.nameSearchForm.controls;
|
||||
}
|
||||
get phoneSearchFormStub(): any {
|
||||
return this.phoneSearchForm.controls;
|
||||
}
|
||||
@@ -54,16 +43,6 @@ export class AccountSearchComponent implements OnInit {
|
||||
return this.addressSearchForm.controls;
|
||||
}
|
||||
|
||||
onNameSearch(): void {
|
||||
this.nameSearchSubmitted = true;
|
||||
if (this.nameSearchForm.invalid) {
|
||||
return;
|
||||
}
|
||||
this.nameSearchLoading = true;
|
||||
this.userService.searchAccountByName(this.nameSearchFormStub.name.value);
|
||||
this.nameSearchLoading = false;
|
||||
}
|
||||
|
||||
async onPhoneSearch(): Promise<void> {
|
||||
this.phoneSearchSubmitted = true;
|
||||
if (this.phoneSearchForm.invalid) {
|
||||
|
||||
@@ -64,6 +64,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<h2 class="text-center"><strong>Loading Accounts!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
class="mat-elevation-z10"
|
||||
[dataSource]="dataSource"
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
@@ -16,7 +22,7 @@ import { AccountDetails } from '@app/_models';
|
||||
styleUrls: ['./accounts.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccountsComponent implements OnInit {
|
||||
export class AccountsComponent implements OnInit, AfterViewInit {
|
||||
dataSource: MatTableDataSource<any>;
|
||||
accounts: Array<AccountDetails> = [];
|
||||
displayedColumns: Array<string> = ['name', 'phone', 'created', 'balance', 'location'];
|
||||
@@ -25,32 +31,33 @@ export class AccountsComponent implements OnInit {
|
||||
accountsType: string = 'all';
|
||||
accountTypes: Array<string>;
|
||||
tokenSymbol: string;
|
||||
loading: boolean = true;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private loggingService: LoggingService,
|
||||
private userService: UserService,
|
||||
private router: Router,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.userService.init();
|
||||
await this.tokenService.init();
|
||||
try {
|
||||
// TODO it feels like this should be in the onInit handler
|
||||
await this.userService.loadAccounts(100);
|
||||
} catch (error) {
|
||||
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error });
|
||||
}
|
||||
this.userService.accountsSubject.subscribe((accounts) => {
|
||||
this.dataSource = new MatTableDataSource<any>(accounts);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
this.accounts = accounts;
|
||||
if (accounts.length > 0) {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
await this.userService.loadAccounts(100);
|
||||
} catch (error) {
|
||||
this.loggingService.sendErrorLevelMessage('Failed to load accounts', this, { error });
|
||||
}
|
||||
this.userService
|
||||
.getAccountTypes()
|
||||
.pipe(first())
|
||||
@@ -62,6 +69,13 @@ export class AccountsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.dataSource) {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
}
|
||||
|
||||
doFilter(value: string): void {
|
||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { AccountSearchComponent } from './account-search/account-search.component';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { AccountHistoryComponent } from './account-history/account-history.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -30,7 +32,9 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
AccountDetailsComponent,
|
||||
CreateAccountComponent,
|
||||
AccountSearchComponent,
|
||||
AccountHistoryComponent,
|
||||
],
|
||||
exports: [AccountHistoryComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
AccountsRoutingModule,
|
||||
@@ -51,6 +55,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
MatProgressSpinnerModule,
|
||||
ReactiveFormsModule,
|
||||
MatSnackBarModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
|
||||
@@ -25,8 +25,7 @@ export class CreateAccountComponent implements OnInit {
|
||||
private userService: UserService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.userService.init();
|
||||
ngOnInit(): void {
|
||||
this.createForm = this.formBuilder.group({
|
||||
accountType: ['', Validators.required],
|
||||
idNumber: ['', Validators.required],
|
||||
@@ -39,6 +38,22 @@ export class CreateAccountComponent implements OnInit {
|
||||
referrer: ['', Validators.required],
|
||||
businessCategory: ['', Validators.required],
|
||||
});
|
||||
this.loadSearchData();
|
||||
}
|
||||
|
||||
get createFormStub(): any {
|
||||
return this.createForm.controls;
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
this.submitted = true;
|
||||
if (this.createForm.invalid || !confirm('Create account?')) {
|
||||
return;
|
||||
}
|
||||
this.submitted = false;
|
||||
}
|
||||
|
||||
loadSearchData(): void {
|
||||
this.userService.getCategories();
|
||||
this.userService.categoriesSubject.subscribe((res) => {
|
||||
this.categories = Object.keys(res);
|
||||
@@ -56,16 +71,4 @@ export class CreateAccountComponent implements OnInit {
|
||||
.pipe(first())
|
||||
.subscribe((res) => (this.genders = res));
|
||||
}
|
||||
|
||||
get createFormStub(): any {
|
||||
return this.createForm.controls;
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
this.submitted = true;
|
||||
if (this.createForm.invalid || !confirm('Create account?')) {
|
||||
return;
|
||||
}
|
||||
this.submitted = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<h2 class="text-center"><strong>Loading Actions!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table class="mat-elevation-z10" [dataSource]="dataSource" multiTemplateDataRows>
|
||||
<!-- Expand Column -->
|
||||
<ng-container matColumnDef="expand">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LoggingService, UserService } from '@app/_services';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { exportCsv } from '@app/_helpers';
|
||||
import { Action } from '../../_models';
|
||||
import { Action } from '@app/_models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin',
|
||||
@@ -26,20 +26,23 @@ export class AdminComponent implements OnInit {
|
||||
displayedColumns: Array<string> = ['expand', 'user', 'role', 'action', 'status', 'approve'];
|
||||
action: Action;
|
||||
actions: Array<Action>;
|
||||
loading: boolean = true;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private userService: UserService, private loggingService: LoggingService) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.userService.init();
|
||||
ngOnInit(): void {
|
||||
this.userService.getActions();
|
||||
this.userService.actionsSubject.subscribe((actions) => {
|
||||
this.dataSource = new MatTableDataSource<any>(actions);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
this.actions = actions;
|
||||
if (actions.length > 0) {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AdminComponent],
|
||||
@@ -29,6 +30,7 @@ import { MatRippleModule } from '@angular/material/core';
|
||||
MatPaginatorModule,
|
||||
MatButtonModule,
|
||||
MatRippleModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export class AdminModule {}
|
||||
|
||||
@@ -22,10 +22,10 @@ const routes: Routes = [
|
||||
path: 'tokens',
|
||||
loadChildren: () => import('@pages/tokens/tokens.module').then((m) => m.TokensModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('@pages/admin/admin.module').then((m) => m.AdminModule),
|
||||
},
|
||||
// {
|
||||
// path: 'admin',
|
||||
// loadChildren: () => import('@pages/admin/admin.module').then((m) => m.AdminModule),
|
||||
// },
|
||||
{ path: '**', redirectTo: 'home', pathMatch: 'full' },
|
||||
];
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 mb-2">
|
||||
<div class="card">
|
||||
<mat-card-title class="card-header">
|
||||
@@ -67,6 +68,12 @@
|
||||
/>
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<h2 class="text-center"><strong>Loading Trusted Users!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
class="mat-elevation-z10"
|
||||
[dataSource]="dataSource"
|
||||
|
||||
@@ -13,24 +13,26 @@ import { exportCsv } from '@app/_helpers';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
date: string;
|
||||
dataSource: MatTableDataSource<any>;
|
||||
displayedColumns: Array<string> = ['name', 'email', 'userId'];
|
||||
trustedUsers: Array<Staff>;
|
||||
userInfo: Staff;
|
||||
loading: boolean = true;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.authService.init();
|
||||
ngOnInit(): void {
|
||||
this.authService.trustedUsersSubject.subscribe((users) => {
|
||||
this.dataSource = new MatTableDataSource<any>(users);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
this.trustedUsers = users;
|
||||
if (users.length > 0) {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
this.userInfo = this.authService.getPrivateKeyInfo();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SettingsComponent, OrganizationComponent],
|
||||
@@ -38,6 +39,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
MatSelectModule,
|
||||
MatMenuModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export class SettingsModule {}
|
||||
|
||||
@@ -45,6 +45,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<h2 class="text-center"><strong>Loading Tokens!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<mat-table
|
||||
class="mat-elevation-z10 table-responsive"
|
||||
[dataSource]="dataSource"
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { LoggingService, TokenService } from '@app/_services';
|
||||
import { TokenService } from '@app/_services';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { exportCsv } from '@app/_helpers';
|
||||
import { Token } from '@app/_models';
|
||||
|
||||
@@ -13,36 +18,41 @@ import { Token } from '@app/_models';
|
||||
styleUrls: ['./tokens.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TokensComponent implements OnInit {
|
||||
export class TokensComponent implements OnInit, AfterViewInit {
|
||||
dataSource: MatTableDataSource<any>;
|
||||
columnsToDisplay: Array<string> = ['name', 'symbol', 'address', 'supply'];
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
tokens: Array<Token>;
|
||||
token: Token;
|
||||
loading: boolean = true;
|
||||
|
||||
constructor(
|
||||
private tokenService: TokenService,
|
||||
private loggingService: LoggingService,
|
||||
private router: Router
|
||||
) {}
|
||||
constructor(private tokenService: TokenService) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.tokenService.init();
|
||||
ngOnInit(): void {
|
||||
this.tokenService.load.subscribe(async (status: boolean) => {
|
||||
if (status) {
|
||||
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;
|
||||
if (tokens.length > 0) {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
if (this.dataSource) {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
}
|
||||
|
||||
doFilter(value: string): void {
|
||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@NgModule({
|
||||
declarations: [TokensComponent, TokenDetailsComponent],
|
||||
@@ -37,6 +38,7 @@ import { MatCardModule } from '@angular/material/card';
|
||||
MatToolbarModule,
|
||||
MatCardModule,
|
||||
MatRippleModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export class TokensModule {}
|
||||
|
||||
@@ -36,9 +36,7 @@ export class TransactionDetailsComponent implements OnInit {
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.transactionService.init();
|
||||
await this.tokenService.init();
|
||||
ngOnInit(): void {
|
||||
if (this.transaction?.type === 'conversion') {
|
||||
this.traderBloxbergLink =
|
||||
'https://blockexplorer.bloxberg.org/address/' + this.transaction?.trader + '/transactions';
|
||||
|
||||
@@ -63,6 +63,11 @@
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<div *ngIf="loading">
|
||||
<h2 class="text-center"><strong>Loading Transactions!</strong></h2>
|
||||
<mat-progress-bar [mode]="'query'"></mat-progress-bar>
|
||||
</div>
|
||||
|
||||
<table
|
||||
mat-table
|
||||
class="mat-elevation-z10"
|
||||
|
||||
@@ -29,6 +29,7 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
|
||||
transactionsType: string = 'all';
|
||||
transactionsTypes: Array<string>;
|
||||
tokenSymbol: string;
|
||||
loading: boolean = true;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
@@ -41,17 +42,16 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.blockSyncService.blockSync();
|
||||
this.transactionService.transactionsSubject.subscribe((transactions) => {
|
||||
this.transactionDataSource = new MatTableDataSource<any>(transactions);
|
||||
this.transactionDataSource.paginator = this.paginator;
|
||||
this.transactionDataSource.sort = this.sort;
|
||||
this.transactions = transactions;
|
||||
if (transactions.length > 0) {
|
||||
this.loading = false;
|
||||
}
|
||||
});
|
||||
await this.blockSyncService.init();
|
||||
await this.tokenService.init();
|
||||
await this.transactionService.init();
|
||||
await this.userService.init();
|
||||
await this.blockSyncService.blockSync();
|
||||
this.userService
|
||||
.getTransactionTypes()
|
||||
.pipe(first())
|
||||
@@ -85,8 +85,10 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.transactionDataSource.paginator = this.paginator;
|
||||
this.transactionDataSource.sort = this.sort;
|
||||
if (this.transactionDataSource) {
|
||||
this.transactionDataSource.paginator = this.paginator;
|
||||
this.transactionDataSource.sort = this.sort;
|
||||
}
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatRippleModule } from '@angular/material/core';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
|
||||
@NgModule({
|
||||
declarations: [TransactionsComponent, TransactionDetailsComponent],
|
||||
@@ -37,6 +38,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
MatCardModule,
|
||||
MatRippleModule,
|
||||
MatSnackBarModule,
|
||||
MatProgressBarModule,
|
||||
],
|
||||
})
|
||||
export class TransactionsModule {}
|
||||
|
||||
8
src/app/shared/_pipes/signature-user.pipe.spec.ts
Normal file
8
src/app/shared/_pipes/signature-user.pipe.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { SignatureUserPipe } from './signature-user.pipe';
|
||||
|
||||
describe('SignatureUserPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new SignatureUserPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
20
src/app/shared/_pipes/signature-user.pipe.ts
Normal file
20
src/app/shared/_pipes/signature-user.pipe.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import * as openpgp from 'openpgp';
|
||||
import { asciiToHex } from '@app/_helpers';
|
||||
import { KeystoreService } from '@app/_services';
|
||||
|
||||
@Pipe({
|
||||
name: 'signatureUser',
|
||||
})
|
||||
export class SignatureUserPipe implements PipeTransform {
|
||||
async transform(armoredSignature: string, ...args: unknown[]): Promise<string> {
|
||||
const keystore = await KeystoreService.getKeystore();
|
||||
const signature = await openpgp.signature.readArmored(armoredSignature);
|
||||
const keyId = asciiToHex(signature.packets[0].issuerKeyId.bytes);
|
||||
const pubKey = keystore.getPublicKeyForId(keyId);
|
||||
if (pubKey) {
|
||||
return pubKey.users[0].userId.userid;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<nav class="navbar navbar-dark background-dark">
|
||||
<h1 class="navbar-brand">
|
||||
<div *ngIf="noInternetConnection; then offlineBlock; else onlineBlock"></div>
|
||||
<div *ngIf="online; then onlineBlock; else offlineBlock"></div>
|
||||
<ng-template #offlineBlock>
|
||||
<strong style="color: red">OFFLINE </strong>
|
||||
<img width="20rem" src="assets/images/no-wifi.svg" alt="Internet Disconnected" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { checkOnlineStatus } from '@src/app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-network-status',
|
||||
@@ -7,21 +8,30 @@ import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class NetworkStatusComponent implements OnInit {
|
||||
noInternetConnection: boolean = !navigator.onLine;
|
||||
online: boolean = navigator.onLine;
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
this.handleNetworkChange();
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
ngOnInit(): void {
|
||||
window.addEventListener('online', (event: any) => {
|
||||
this.online = true;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
window.addEventListener('offline', (event: any) => {
|
||||
this.online = false;
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
handleNetworkChange(): void {
|
||||
setTimeout(() => {
|
||||
if (!navigator.onLine !== this.noInternetConnection) {
|
||||
this.noInternetConnection = !navigator.onLine;
|
||||
setTimeout(async () => {
|
||||
if (this.online !== (await checkOnlineStatus())) {
|
||||
this.online = await checkOnlineStatus();
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
this.handleNetworkChange();
|
||||
}, 5000);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { SafePipe } from '@app/shared/_pipes/safe.pipe';
|
||||
import { NetworkStatusComponent } from './network-status/network-status.component';
|
||||
import { UnixDatePipe } from './_pipes/unix-date.pipe';
|
||||
import { SignatureUserPipe } from './_pipes/signature-user.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -26,6 +27,7 @@ import { UnixDatePipe } from './_pipes/unix-date.pipe';
|
||||
SafePipe,
|
||||
NetworkStatusComponent,
|
||||
UnixDatePipe,
|
||||
SignatureUserPipe,
|
||||
],
|
||||
exports: [
|
||||
TopbarComponent,
|
||||
@@ -36,6 +38,7 @@ import { UnixDatePipe } from './_pipes/unix-date.pipe';
|
||||
SafePipe,
|
||||
NetworkStatusComponent,
|
||||
UnixDatePipe,
|
||||
SignatureUserPipe,
|
||||
],
|
||||
imports: [CommonModule, RouterModule, MatIconModule, MatDialogModule],
|
||||
})
|
||||
|
||||
@@ -6,11 +6,11 @@ export const environment = {
|
||||
logLevel: NgxLoggerLevel.DEBUG,
|
||||
serverLogLevel: NgxLoggerLevel.OFF,
|
||||
loggingUrl: '',
|
||||
cicMetaUrl: 'https://meta-auth.dev.grassrootseconomics.net:443',
|
||||
cicMetaUrl: 'http://localhost:63380',
|
||||
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
||||
cicCacheUrl: 'https://cache.dev.grassrootseconomics.net',
|
||||
web3Provider: 'wss://bloxberg-ws.dev.grassrootseconomics.net',
|
||||
cicUssdUrl: 'https://user.dev.grassrootseconomics.net',
|
||||
cicCacheUrl: 'http://localhost:63313',
|
||||
web3Provider: 'http://localhost:8545',
|
||||
cicUssdUrl: 'http://localhost:63415',
|
||||
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
||||
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
||||
dashboardUrl: 'https://dashboard.sarafu.network/',
|
||||
|
||||
17
src/environments/environment.staging.ts
Normal file
17
src/environments/environment.staging.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { NgxLoggerLevel } from 'ngx-logger';
|
||||
|
||||
export const environment = {
|
||||
production: false,
|
||||
bloxbergChainId: 8996,
|
||||
logLevel: NgxLoggerLevel.DEBUG,
|
||||
serverLogLevel: NgxLoggerLevel.OFF,
|
||||
loggingUrl: '',
|
||||
cicMetaUrl: 'https://meta.staging.grassrootseconomics.net',
|
||||
publicKeysUrl: 'https://dev.grassrootseconomics.net/.well-known/publickeys/',
|
||||
cicCacheUrl: 'https://cache.staging.grassrootseconomics.net',
|
||||
web3Provider: 'wss://bloxberg.staging.grassrootseconomics.net',
|
||||
cicUssdUrl: 'https://user.staging.grassrootseconomics.net',
|
||||
registryAddress: '0xea6225212005e86a4490018ded4bf37f3e772161',
|
||||
trustedDeclaratorAddress: '0xEb3907eCad74a0013c259D5874AE7f22DcBcC95C',
|
||||
dashboardUrl: 'https://dashboard.sarafu.network/',
|
||||
};
|
||||
15
tsconfig.worker.json
Normal file
15
tsconfig.worker.json
Normal file
@@ -0,0 +1,15 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/worker",
|
||||
"lib": [
|
||||
"es2018",
|
||||
"webworker"
|
||||
],
|
||||
"types": []
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.worker.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user