From 432fb9cd6698789ba5c85d0d93ec84eee8f9d554 Mon Sep 17 00:00:00 2001 From: softsimon Date: Mon, 13 Jul 2020 21:46:25 +0700 Subject: [PATCH] Address index and api. Address view. --- backend/src/api/bisq.ts | 51 +++++++-- backend/src/index.ts | 1 + backend/src/routes.ts | 9 ++ frontend/src/app/app.module.ts | 2 - .../bisq-address/bisq-address.component.html | 106 ++++++++++++++++++ .../bisq-address/bisq-address.component.scss | 23 ++++ .../bisq-address/bisq-address.component.ts | 82 ++++++++++++++ frontend/src/app/bisq/bisq-api.service.ts | 4 + .../bisq/bisq-block/bisq-block.component.html | 2 +- .../bisq-block/bisq-block.component.spec.ts | 25 ----- .../bisq/bisq-block/bisq-block.component.ts | 4 + .../bisq-blocks/bisq-blocks.component.html | 2 +- .../bisq/bisq-blocks/bisq-blocks.component.ts | 3 + .../bisq-transaction.component.html | 2 +- .../bisq-transaction.component.ts | 4 + .../bisq-transactions.component.html | 2 +- .../bisq-transactions.component.ts | 4 + .../bisq-transfers.component.html | 4 +- frontend/src/app/bisq/bisq.module.ts | 2 + frontend/src/app/bisq/bisq.routing.module.ts | 3 +- frontend/src/app/services/seo.service.ts | 9 +- frontend/src/app/shared/shared.module.ts | 5 +- 22 files changed, 295 insertions(+), 54 deletions(-) create mode 100644 frontend/src/app/bisq/bisq-address/bisq-address.component.html create mode 100644 frontend/src/app/bisq/bisq-address/bisq-address.component.scss create mode 100644 frontend/src/app/bisq/bisq-address/bisq-address.component.ts delete mode 100644 frontend/src/app/bisq/bisq-block/bisq-block.component.spec.ts diff --git a/backend/src/api/bisq.ts b/backend/src/api/bisq.ts index 61a27de8d..4bca8f4c6 100644 --- a/backend/src/api/bisq.ts +++ b/backend/src/api/bisq.ts @@ -3,11 +3,11 @@ import * as fs from 'fs'; import { BisqBlocks, BisqBlock, BisqTransaction } from '../interfaces'; class Bisq { - private latestBlockHeight = 0; private blocks: BisqBlock[] = []; private transactions: BisqTransaction[] = []; - private transactionsIndex: { [txId: string]: BisqTransaction } = {}; - private blocksIndex: { [hash: string]: BisqBlock } = {}; + private transactionIndex: { [txId: string]: BisqTransaction } = {}; + private blockIndex: { [hash: string]: BisqBlock } = {}; + private addressIndex: { [address: string]: BisqTransaction[] } = {}; constructor() {} @@ -29,7 +29,7 @@ class Bisq { } getTransaction(txId: string): BisqTransaction | undefined { - return this.transactionsIndex[txId]; + return this.transactionIndex[txId]; } getTransactions(start: number, length: number): [BisqTransaction[], number] { @@ -37,9 +37,11 @@ class Bisq { } getBlock(hash: string): BisqBlock | undefined { - console.log(hash); - console.log(this.blocksIndex[hash]); - return this.blocksIndex[hash]; + return this.blockIndex[hash]; + } + + getAddress(hash: string): BisqTransaction[] { + return this.addressIndex[hash]; } getBlocks(start: number, length: number): [BisqBlock[], number] { @@ -59,16 +61,42 @@ class Bisq { private buildIndex() { const start = new Date().getTime(); this.transactions = []; - this.transactionsIndex = {}; + this.transactionIndex = {}; + this.addressIndex = {}; + this.blocks.forEach((block) => { - if (!this.blocksIndex[block.hash]) { - this.blocksIndex[block.hash] = block; + /* Build block index */ + if (!this.blockIndex[block.hash]) { + this.blockIndex[block.hash] = block; } + + /* Build transactions index */ block.txs.forEach((tx) => { this.transactions.push(tx); - this.transactionsIndex[tx.id] = tx; + this.transactionIndex[tx.id] = tx; }); }); + + /* Build address index */ + this.transactions.forEach((tx) => { + tx.inputs.forEach((input) => { + if (!this.addressIndex[input.address]) { + this.addressIndex[input.address] = []; + } + if (this.addressIndex[input.address].indexOf(tx) === -1) { + this.addressIndex[input.address].push(tx); + } + }); + tx.outputs.forEach((output) => { + if (!this.addressIndex[output.address]) { + this.addressIndex[output.address] = []; + } + if (this.addressIndex[output.address].indexOf(tx) === -1) { + this.addressIndex[output.address].push(tx); + } + }); + }); + const time = new Date().getTime() - start; console.log('Bisq data index rebuilt in ' + time + ' ms'); } @@ -81,7 +109,6 @@ class Bisq { if (data.blocks && data.blocks.length !== this.blocks.length) { this.blocks = data.blocks; this.blocks.reverse(); - this.latestBlockHeight = data.chainHeight; const time = new Date().getTime() - start; console.log('Bisq dump loaded in ' + time + ' ms'); } else { diff --git a/backend/src/index.ts b/backend/src/index.ts index ae4796a37..1ac1fd45d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -95,6 +95,7 @@ class Server { .get(config.API_ENDPOINT + 'bisq/tx/:txId', routes.getBisqTransaction) .get(config.API_ENDPOINT + 'bisq/block/:hash', routes.getBisqBlock) .get(config.API_ENDPOINT + 'bisq/blocks/:index/:length', routes.getBisqBlocks) + .get(config.API_ENDPOINT + 'bisq/address/:address', routes.getBisqAddress) .get(config.API_ENDPOINT + 'bisq/txs/:index/:length', routes.getBisqTransactions) ; } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 271a70876..aed87f0be 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -120,6 +120,15 @@ class Routes { res.header('X-Total-Count', count.toString()); res.send(transactions); } + + public getBisqAddress(req: Request, res: Response) { + const result = bisq.getAddress(req.params.address.substr(1)); + if (result) { + res.send(result); + } else { + res.status(404).send('Bisq address not found'); + } + } } export default new Routes(); diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 11d0aed61..8e11f8383 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -22,7 +22,6 @@ import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks. import { WebsocketService } from './services/websocket.service'; import { AddressLabelsComponent } from './components/address-labels/address-labels.component'; import { MempoolBlocksComponent } from './components/mempool-blocks/mempool-blocks.component'; -import { QrcodeComponent } from './components/qrcode/qrcode.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; import { AboutComponent } from './components/about/about.component'; import { TelevisionComponent } from './components/television/television.component'; @@ -64,7 +63,6 @@ import { SharedModule } from './shared/shared.module'; TimespanComponent, AddressLabelsComponent, MempoolBlocksComponent, - QrcodeComponent, ChartistComponent, FooterComponent, FiatComponent, diff --git a/frontend/src/app/bisq/bisq-address/bisq-address.component.html b/frontend/src/app/bisq/bisq-address/bisq-address.component.html new file mode 100644 index 000000000..b6422e867 --- /dev/null +++ b/frontend/src/app/bisq/bisq-address/bisq-address.component.html @@ -0,0 +1,106 @@ +
+

Bisq Address

+ + {{ addressString | shortenString : 24 }} + {{ addressString }} + + +
+ +
+ + +
+ +
+
+ + + + + + + + + + + + + + + +
Total received{{ totalReceived / 100 }} BSQ
Total sent{{ totalSent / 100 }} BSQ
Final balance{{ (totalReceived - totalSent) / 100 }} BSQ
+
+
+
+
+ +
+
+
+ +
+ +
+ +

{{ transactions.length | number }} transactions

+ + + +
+ + {{ tx.id | shortenString : 16 }} + {{ tx.id }} + +
+ {{ tx.time | date:'yyyy-MM-dd HH:mm' }} +
+
+
+ + + +
+
+ +
+ + + +
+
+
+ + + + + + + + + + + + +
+
+
+
+ +
+
+
+ +
+ + +
+ Error loading address data. +
+ {{ error.error }} +
+
+ +
+ +
\ No newline at end of file diff --git a/frontend/src/app/bisq/bisq-address/bisq-address.component.scss b/frontend/src/app/bisq/bisq-address/bisq-address.component.scss new file mode 100644 index 000000000..c5961e428 --- /dev/null +++ b/frontend/src/app/bisq/bisq-address/bisq-address.component.scss @@ -0,0 +1,23 @@ +.qr-wrapper { + background-color: #FFF; + padding: 10px; + padding-bottom: 5px; + display: inline-block; + margin-right: 25px; +} + +@media (min-width: 576px) { + .qrcode-col { + text-align: right; + } +} +@media (max-width: 575.98px) { + .qrcode-col { + text-align: center; + } + + .qrcode-col > div { + margin-top: 20px; + margin-right: 0px; + } +} \ No newline at end of file diff --git a/frontend/src/app/bisq/bisq-address/bisq-address.component.ts b/frontend/src/app/bisq/bisq-address/bisq-address.component.ts new file mode 100644 index 000000000..a82a31e58 --- /dev/null +++ b/frontend/src/app/bisq/bisq-address/bisq-address.component.ts @@ -0,0 +1,82 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { SeoService } from 'src/app/services/seo.service'; +import { switchMap, filter, catchError } from 'rxjs/operators'; +import { ParamMap, ActivatedRoute } from '@angular/router'; +import { Subscription, of } from 'rxjs'; +import { BisqTransaction } from '../bisq.interfaces'; +import { BisqApiService } from '../bisq-api.service'; + +@Component({ + selector: 'app-bisq-address', + templateUrl: './bisq-address.component.html', + styleUrls: ['./bisq-address.component.scss'] +}) +export class BisqAddressComponent implements OnInit, OnDestroy { + transactions: BisqTransaction[]; + addressString: string; + isLoadingAddress = true; + error: any; + mainSubscription: Subscription; + + totalReceived = 0; + totalSent = 0; + + constructor( + private route: ActivatedRoute, + private seoService: SeoService, + private bisqApiService: BisqApiService, + ) { } + + ngOnInit() { + this.mainSubscription = this.route.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.error = undefined; + this.isLoadingAddress = true; + this.transactions = null; + document.body.scrollTo(0, 0); + this.addressString = params.get('id') || ''; + this.seoService.setTitle('Address: ' + this.addressString, true); + + return this.bisqApiService.getAddress$(this.addressString) + .pipe( + catchError((err) => { + this.isLoadingAddress = false; + this.error = err; + console.log(err); + return of(null); + }) + ); + }), + filter((transactions) => transactions !== null) + ) + .subscribe((transactions: BisqTransaction[]) => { + this.transactions = transactions; + this.updateChainStats(); + this.isLoadingAddress = false; + }, + (error) => { + console.log(error); + this.error = error; + this.isLoadingAddress = false; + }); + } + + updateChainStats() { + const shortenedAddress = this.addressString.substr(1); + + this.totalSent = this.transactions.reduce((acc, tx) => + acc + tx.inputs + .filter((input) => input.address === shortenedAddress) + .reduce((a, input) => a + input.bsqAmount, 0), 0); + + this.totalReceived = this.transactions.reduce((acc, tx) => + acc + tx.outputs + .filter((output) => output.address === shortenedAddress) + .reduce((a, output) => a + output.bsqAmount, 0), 0); + } + + ngOnDestroy() { + this.mainSubscription.unsubscribe(); + } +} diff --git a/frontend/src/app/bisq/bisq-api.service.ts b/frontend/src/app/bisq/bisq-api.service.ts index c1bf92420..f4ab802b0 100644 --- a/frontend/src/app/bisq/bisq-api.service.ts +++ b/frontend/src/app/bisq/bisq-api.service.ts @@ -30,4 +30,8 @@ export class BisqApiService { listBlocks$(start: number, length: number): Observable> { return this.httpClient.get(API_BASE_URL + `/bisq/blocks/${start}/${length}`, { observe: 'response' }); } + + getAddress$(address: string): Observable { + return this.httpClient.get(API_BASE_URL + '/bisq/address/' + address); + } } diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.html b/frontend/src/app/bisq/bisq-block/bisq-block.component.html index f2fd3c7cd..49ad92e2d 100644 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.html +++ b/frontend/src/app/bisq/bisq-block/bisq-block.component.html @@ -1,7 +1,7 @@
-

Block {{ blockHeight }}

+

Bisq Block {{ blockHeight }}

diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.spec.ts b/frontend/src/app/bisq/bisq-block/bisq-block.component.spec.ts deleted file mode 100644 index f015a36fe..000000000 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { BisqBlockComponent } from './bisq-block.component'; - -describe('BisqBlockComponent', () => { - let component: BisqBlockComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ BisqBlockComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(BisqBlockComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts index c09a6658c..9616c76f2 100644 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts +++ b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts @@ -4,6 +4,7 @@ import { BisqApiService } from '../bisq-api.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { Subscribable, Subscription, of } from 'rxjs'; import { switchMap } from 'rxjs/operators'; +import { SeoService } from 'src/app/services/seo.service'; @Component({ selector: 'app-bisq-block', @@ -21,6 +22,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { constructor( private bisqApiService: BisqApiService, private route: ActivatedRoute, + private seoService: SeoService, ) { } ngOnInit(): void { @@ -28,6 +30,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { .pipe( switchMap((params: ParamMap) => { this.blockHash = params.get('id') || ''; + document.body.scrollTo(0, 0); this.isLoading = true; if (history.state.data && history.state.data.blockHeight) { this.blockHeight = history.state.data.blockHeight; @@ -42,6 +45,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { .subscribe((block: BisqBlock) => { this.isLoading = false; this.blockHeight = block.height; + this.seoService.setTitle('Block: #' + block.height + ': ' + block.hash, true); this.block = block; }); } diff --git a/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.html b/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.html index a5a01f8a8..11aa8d0d5 100644 --- a/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.html +++ b/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.html @@ -1,5 +1,5 @@
-

BSQ Blocks

+

Bisq Blocks


diff --git a/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.ts b/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.ts index 168b9d040..e8e4a9720 100644 --- a/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.ts +++ b/frontend/src/app/bisq/bisq-blocks/bisq-blocks.component.ts @@ -3,6 +3,7 @@ import { BisqApiService } from '../bisq-api.service'; import { switchMap } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces'; +import { SeoService } from 'src/app/services/seo.service'; @Component({ selector: 'app-bisq-blocks', @@ -21,9 +22,11 @@ export class BisqBlocksComponent implements OnInit { constructor( private bisqApiService: BisqApiService, + private seoService: SeoService, ) { } ngOnInit(): void { + this.seoService.setTitle('Blocks', true); this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); this.pageSubject$ diff --git a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html index 3625fcceb..24e2a3653 100644 --- a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html +++ b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -1,6 +1,6 @@
-

Transaction

+

Bisq Transaction

diff --git a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts index f79385940..af7f764e5 100644 --- a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts +++ b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.ts @@ -6,6 +6,7 @@ import { of, Observable, Subscription } from 'rxjs'; import { StateService } from 'src/app/services/state.service'; import { Block } from 'src/app/interfaces/electrs.interface'; import { BisqApiService } from '../bisq-api.service'; +import { SeoService } from 'src/app/services/seo.service'; @Component({ selector: 'app-bisq-transaction', @@ -23,13 +24,16 @@ export class BisqTransactionComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private bisqApiService: BisqApiService, private stateService: StateService, + private seoService: SeoService, ) { } ngOnInit(): void { this.subscription = this.route.paramMap.pipe( switchMap((params: ParamMap) => { this.isLoading = true; + document.body.scrollTo(0, 0); this.txId = params.get('id') || ''; + this.seoService.setTitle('Transaction: ' + this.txId, true); if (history.state.data) { return of(history.state.data); } diff --git a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html index b882f3456..9d6629a19 100644 --- a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html +++ b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html @@ -1,5 +1,5 @@
-

BSQ Transactions

+

Bisq Transactions


diff --git a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.ts b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.ts index 232a3ca5b..08008e837 100644 --- a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.ts +++ b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.ts @@ -3,6 +3,7 @@ import { BisqTransaction, BisqOutput } from '../bisq.interfaces'; import { Subject } from 'rxjs'; import { switchMap } from 'rxjs/operators'; import { BisqApiService } from '../bisq-api.service'; +import { SeoService } from 'src/app/services/seo.service'; @Component({ selector: 'app-bisq-transactions', @@ -21,9 +22,12 @@ export class BisqTransactionsComponent implements OnInit { constructor( private bisqApiService: BisqApiService, + private seoService: SeoService, ) { } ngOnInit(): void { + this.seoService.setTitle('Transactions', true); + this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); this.pageSubject$ diff --git a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html index 828b03e18..45cc0e971 100644 --- a/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html +++ b/frontend/src/app/bisq/bisq-transfers/bisq-transfers.component.html @@ -16,7 +16,7 @@ - + B{{ input.address | shortenString : 16 }} B{{ input.address | shortenString : 35 }} @@ -36,7 +36,7 @@ - + B{{ output.address | shortenString : 16 }} B{{ output.address | shortenString : 35 }} diff --git a/frontend/src/app/bisq/bisq.module.ts b/frontend/src/app/bisq/bisq.module.ts index ca02919f9..8ce14bc34 100644 --- a/frontend/src/app/bisq/bisq.module.ts +++ b/frontend/src/app/bisq/bisq.module.ts @@ -15,6 +15,7 @@ import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileA import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component'; import { BisqApiService } from './bisq-api.service'; +import { BisqAddressComponent } from './bisq-address/bisq-address.component'; @NgModule({ declarations: [ @@ -27,6 +28,7 @@ import { BisqApiService } from './bisq-api.service'; BisqTransfersComponent, BisqBlocksComponent, BisqExplorerComponent, + BisqAddressComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/bisq/bisq.routing.module.ts b/frontend/src/app/bisq/bisq.routing.module.ts index f4166c066..882056a58 100644 --- a/frontend/src/app/bisq/bisq.routing.module.ts +++ b/frontend/src/app/bisq/bisq.routing.module.ts @@ -7,6 +7,7 @@ import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.co import { BisqBlockComponent } from './bisq-block/bisq-block.component'; import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component'; +import { BisqAddressComponent } from './bisq-address/bisq-address.component'; const routes: Routes = [ { @@ -32,7 +33,7 @@ const routes: Routes = [ }, { path: 'address/:id', - component: AddressComponent + component: BisqAddressComponent, }, { path: 'about', diff --git a/frontend/src/app/services/seo.service.ts b/frontend/src/app/services/seo.service.ts index f07359a4e..455775d19 100644 --- a/frontend/src/app/services/seo.service.ts +++ b/frontend/src/app/services/seo.service.ts @@ -18,14 +18,9 @@ export class SeoService { setTitle(newTitle: string, prependNetwork = false) { let networkName = ''; - if (prependNetwork) { - if (this.network === 'liquid') { - networkName = 'Liquid '; - } else if (this.network === 'testnet') { - networkName = 'Testnet '; - } + if (prependNetwork && this.network !== '') { + networkName = this.network.substr(0, 1).toUpperCase() + this.network.substr(1) + ' '; } - this.titleService.setTitle(networkName + newTitle + ' - ' + this.defaultTitle); } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 003626b6c..3c6c656dc 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -10,6 +10,7 @@ import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe'; import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe'; import { TimeSinceComponent } from '../components/time-since/time-since.component'; import { ClipboardComponent } from '../components/clipboard/clipboard.component'; +import { QrcodeComponent } from '../components/qrcode/qrcode.component'; @NgModule({ declarations: [ @@ -23,6 +24,7 @@ import { ClipboardComponent } from '../components/clipboard/clipboard.component' ShortenStringPipe, ClipboardComponent, TimeSinceComponent, + QrcodeComponent, ], imports: [ CommonModule, @@ -40,7 +42,8 @@ import { ClipboardComponent } from '../components/clipboard/clipboard.component' CeilPipe, ShortenStringPipe, TimeSinceComponent, - ClipboardComponent + ClipboardComponent, + QrcodeComponent, ] }) export class SharedModule {}