Merge branch 'spencer/refactor-data-load-process' into 'master'

Improve data load process to occur on app load.

See merge request grassrootseconomics/cic-staff-client!38
This commit is contained in:
Spencer Ofwiti 2021-07-13 16:57:29 +00:00
commit 56a3d79aea
44 changed files with 1204 additions and 151 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
/dist
/tmp
/out-tsc
/tests
# Only exists if Bazel was run
/bazel-out

View 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&lt;unknown&gt;</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&lt;HttpEvent&lt;unknown&gt;&gt;</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 &#x27;@angular/common/http&#x27;;
import { Injectable } from &#x27;@angular/core&#x27;;
// Third party imports
import { Observable } from &#x27;rxjs&#x27;;
// Application imports
import { LoggingService } from &#x27;@app/_services/logging.service&#x27;;
import { checkOnlineStatus } from &#x27;@app/_helpers&#x27;;
/** 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&lt;unknown&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;unknown&gt;&gt; {
checkOnlineStatus().then((online) &#x3D;&gt; {
if (!online) {
this.loggingService.sendErrorLevelMessage(&#x27;No internet connection on device!&#x27;, this, {
error: &#x60;NetworkError when attempting to fetch resource ${request.url}.&#x60;,
});
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>

View File

@ -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">&lt;</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</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">&lt;</span><span class="tsd-signature-type">HttpEvent</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</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">&lt;</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</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">&lt;</span><span class="tsd-signature-type">HttpEvent</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</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>

View 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">&lt;</span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">&gt;</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">&lt;</span><span class="tsd-signature-type">boolean</span><span class="tsd-signature-symbol">&gt;</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>

View File

@ -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>

View File

@ -9,3 +9,4 @@ 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';

View 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 };

View 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();
});
});

View 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);
}
}

View File

@ -59,9 +59,10 @@ export class ErrorInterceptor implements HttpInterceptor {
this.router.navigateByUrl('/auth').then();
break;
case 403: // forbidden
this.errorDialogService.openDialog(
{ message: 'Access to resource is not allowed (Error 403)'})
//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.

View File

@ -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';

View File

@ -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,7 +46,7 @@ export class AuthService {
}
getWithToken(): Promise<boolean> {
const sessionToken = this.getSessionToken()
const sessionToken = this.getSessionToken();
const headers = {
Authorization: 'Bearer ' + sessionToken,
'Content-Type': 'application/json;charset=utf-8',
@ -94,33 +92,32 @@ export class AuthService {
async login(): Promise<boolean> {
if (this.getSessionToken()) {
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
}
}
const o = await this.getChallenge();
const r = await signChallenge(
o.challenge,
o.realm,
environment.cicMetaUrl,
this.mutableKeyStore
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);
}
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');
// this.setState('Click button to log in');
return true;
}
return false;

View File

@ -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;

View File

@ -35,6 +35,7 @@ export class LocationService {
return queriedAreaName;
}
}
return 'other';
}
getAreaTypes(): void {
@ -54,5 +55,6 @@ export class LocationService {
return queriedAreaType;
}
}
return 'other';
}
}

View File

@ -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()) {

View File

@ -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);
}

View File

@ -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();
}

View File

@ -7,11 +7,9 @@ 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 { KeystoreService } from '@app/_services/keystore.service';
@ -43,13 +41,10 @@ export class UserService {
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();
@ -203,12 +198,6 @@ 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);
@ -271,10 +260,6 @@ export class UserService {
this.accountsList.next(this.accounts);
}
searchAccountByName(name: string): any {
return;
}
getCategories(): void {
this.httpClient
.get(`${environment.cicMetaUrl}/categories`)
@ -292,6 +277,7 @@ export class UserService {
return queriedCategory;
}
}
return 'other';
}
getAccountTypes(): Observable<any> {

View File

@ -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,7 +41,34 @@ 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();
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 {
// 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 });
}
}
if (!this.url.includes('tokens')) {
this.tokenService.load.subscribe(async (status: boolean) => {
if (status) {
await this.tokenService.getTokens();
}
});
}
}
});
try {
const publicKeys = await this.authService.getPublicKeys();
await this.authService.mutableKeyStore.importPublicKey(publicKeys);

View File

@ -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 },

View File

@ -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: "Failed to login please try again.",
message: 'Failed to login please try again.',
});
}
}

View File

@ -382,6 +382,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 +496,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"

View File

@ -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;
@ -68,6 +69,8 @@ export class AccountDetailsComponent implements OnInit {
category: string;
area: string;
areaType: string;
accountsLoading: boolean = true;
transactionsLoading: boolean = true;
constructor(
private formBuilder: FormBuilder,
@ -103,10 +106,7 @@ export class AccountDetailsComponent implements OnInit {
location: ['', Validators.required],
locationType: ['', Validators.required],
});
await this.blockSyncService.init();
await this.tokenService.init();
await this.transactionService.init();
await this.userService.init();
this.transactionService.resetTransactionsList();
await this.blockSyncService.blockSync(this.accountAddress);
this.userService.resetAccountsList();
(await this.userService.getAccountByAddress(this.accountAddress, 100)).subscribe(
@ -114,7 +114,6 @@ export class AccountDetailsComponent implements OnInit {
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,
@ -158,6 +157,9 @@ export class AccountDetailsComponent implements OnInit {
this.userDataSource.paginator = this.userTablePaginator;
this.userDataSource.sort = this.userTableSort;
this.accounts = accounts;
if (accounts.length > 0) {
this.accountsLoading = false;
}
this.cdr.detectChanges();
});
@ -166,6 +168,9 @@ export class AccountDetailsComponent implements OnInit {
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.getCategories();
@ -199,6 +204,17 @@ 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;
}
}
doTransactionFilter(value: string): void {
this.transactionsDataSource.filter = value.trim().toLocaleLowerCase();
}

View File

@ -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) {

View File

@ -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"

View File

@ -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,34 @@ 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();
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 {
// 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;
});
this.userService
.getAccountTypes()
.pipe(first())
@ -62,6 +70,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();
}

View File

@ -23,6 +23,7 @@ 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';
@NgModule({
declarations: [
@ -51,6 +52,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
MatProgressSpinnerModule,
ReactiveFormsModule,
MatSnackBarModule,
MatProgressBarModule,
],
})
export class AccountsModule {}

View File

@ -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],

View File

@ -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">

View File

@ -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;
}
});
}

View File

@ -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 {}

View File

@ -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' },
];

View File

@ -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"

View File

@ -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();
}

View File

@ -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 {}

View File

@ -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"

View File

@ -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();
}

View File

@ -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 {}

View File

@ -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';

View File

@ -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"

View File

@ -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 {

View File

@ -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 {}

View File

@ -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" />

View File

@ -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);
}
}