Implement create item
Adds Zcash price feed from CoinGecko
This commit is contained in:
parent
22c89b9d4f
commit
cd1a0208df
14 changed files with 207 additions and 31 deletions
|
@ -7,6 +7,7 @@ const ownermodel = require('./models/owner');
|
||||||
const itemmodel = require('./models/item');
|
const itemmodel = require('./models/item');
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const stdrpc = require('stdrpc');
|
const stdrpc = require('stdrpc');
|
||||||
|
const CoinGecko = require('coingecko-api');
|
||||||
//const RequestIP = require('@supercharge/request-ip');
|
//const RequestIP = require('@supercharge/request-ip');
|
||||||
|
|
||||||
var db = require('./config/db');
|
var db = require('./config/db');
|
||||||
|
@ -23,6 +24,8 @@ const rpc = stdrpc({
|
||||||
password: fullnode.password
|
password: fullnode.password
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CoinGeckoClient = new CoinGecko();
|
||||||
|
|
||||||
app.use(bodyparser.json());
|
app.use(bodyparser.json());
|
||||||
|
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
@ -194,4 +197,28 @@ app.get('/api/getitems', (req, res, next) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.post('/api/item', (req, res, next) => {
|
||||||
|
console.log('Post: /api/item');
|
||||||
|
const item = new itemmodel(req.body.item);
|
||||||
|
item.save();
|
||||||
|
res.status(201).json({
|
||||||
|
message: 'Item added'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/api/price', (req, res, next) => {
|
||||||
|
console.log('Get /api/price');
|
||||||
|
CoinGeckoClient.simple.price({
|
||||||
|
ids: ['zcash'],
|
||||||
|
vs_currencies: ['usd']
|
||||||
|
}).
|
||||||
|
then((data) => {
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: 'price found!',
|
||||||
|
price: data.data.zcash.usd
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|
11
package-lock.json
generated
11
package-lock.json
generated
|
@ -20,6 +20,7 @@
|
||||||
"@angular/router": "~12.2.0",
|
"@angular/router": "~12.2.0",
|
||||||
"@supercharge/request-ip": "^1.1.2",
|
"@supercharge/request-ip": "^1.1.2",
|
||||||
"angular-local-storage": "^0.7.1",
|
"angular-local-storage": "^0.7.1",
|
||||||
|
"coingecko-api": "^1.0.10",
|
||||||
"easyqrcodejs": "^4.4.6",
|
"easyqrcodejs": "^4.4.6",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
@ -4127,6 +4128,11 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/coingecko-api": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/coingecko-api/-/coingecko-api-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-7YLLC85+daxAw5QlBWoHVBVpJRwoPr4HtwanCr8V/WRjoyHTa1Lb9DQAvv4MDJZHiz4no6HGnDQnddtjV35oRA=="
|
||||||
|
},
|
||||||
"node_modules/collection-visit": {
|
"node_modules/collection-visit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||||
|
@ -18668,6 +18674,11 @@
|
||||||
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"coingecko-api": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/coingecko-api/-/coingecko-api-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-7YLLC85+daxAw5QlBWoHVBVpJRwoPr4HtwanCr8V/WRjoyHTa1Lb9DQAvv4MDJZHiz4no6HGnDQnddtjV35oRA=="
|
||||||
|
},
|
||||||
"collection-visit": {
|
"collection-visit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
"@angular/router": "~12.2.0",
|
"@angular/router": "~12.2.0",
|
||||||
"@supercharge/request-ip": "^1.1.2",
|
"@supercharge/request-ip": "^1.1.2",
|
||||||
"angular-local-storage": "^0.7.1",
|
"angular-local-storage": "^0.7.1",
|
||||||
|
"coingecko-api": "^1.0.10",
|
||||||
"easyqrcodejs": "^4.4.6",
|
"easyqrcodejs": "^4.4.6",
|
||||||
"rxjs": "~6.6.0",
|
"rxjs": "~6.6.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
@ -15,6 +15,7 @@ import { HeaderComponent } from './header/header.component';
|
||||||
import { ViewerComponent } from './viewer/viewer.component';
|
import { ViewerComponent } from './viewer/viewer.component';
|
||||||
import { LoginComponent } from './login/login.component';
|
import { LoginComponent } from './login/login.component';
|
||||||
import { ItemListComponent } from './items/item-list/item-list.component';
|
import { ItemListComponent } from './items/item-list/item-list.component';
|
||||||
|
import { ItemCreateComponent } from './items/item-create/item-create.component';
|
||||||
//import { NameDialogComponent } from './namedialog/namedialog.component';
|
//import { NameDialogComponent } from './namedialog/namedialog.component';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
@ -24,13 +25,15 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
ViewerComponent,
|
ViewerComponent,
|
||||||
ItemListComponent,
|
ItemListComponent,
|
||||||
LoginComponent
|
LoginComponent,
|
||||||
|
ItemCreateComponent
|
||||||
//NameDialogComponent,
|
//NameDialogComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
@ -41,7 +44,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
BrowserAnimationsModule
|
BrowserAnimationsModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]//,
|
bootstrap: [AppComponent],
|
||||||
//entryComponents: [NameDialogComponent]
|
entryComponents: [ItemCreateComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|
|
@ -7,19 +7,22 @@ import {UserService} from './user.service';
|
||||||
|
|
||||||
@Injectable({providedIn: 'root'})
|
@Injectable({providedIn: 'root'})
|
||||||
export class FullnodeService{
|
export class FullnodeService{
|
||||||
private dataStore: { height: number, memoList: string[], addr: string } = { height: 0, memoList: [], addr: '' };
|
private dataStore: { height: number, memoList: string[], addr: string, price: number } = { height: 0, memoList: [], addr: '', price:0 };
|
||||||
private _heightUpdated: BehaviorSubject<number> = new BehaviorSubject(this.dataStore.height);
|
private _heightUpdated: BehaviorSubject<number> = new BehaviorSubject(this.dataStore.height);
|
||||||
private _memoUpdated: BehaviorSubject<string[]> = new BehaviorSubject(this.dataStore.memoList);
|
private _memoUpdated: BehaviorSubject<string[]> = new BehaviorSubject(this.dataStore.memoList);
|
||||||
private _addrUpdated: BehaviorSubject<string> = new BehaviorSubject(this.dataStore.addr);
|
private _addrUpdated: BehaviorSubject<string> = new BehaviorSubject(this.dataStore.addr);
|
||||||
|
private _priceUpdated: BehaviorSubject<number> = new BehaviorSubject(this.dataStore.price);
|
||||||
public readonly addrUpdate: Observable<string> = this._addrUpdated.asObservable();
|
public readonly addrUpdate: Observable<string> = this._addrUpdated.asObservable();
|
||||||
public readonly heightUpdate: Observable<number> = this._heightUpdated.asObservable();
|
public readonly heightUpdate: Observable<number> = this._heightUpdated.asObservable();
|
||||||
public readonly memoUpdate: Observable<string[]> = this._memoUpdated.asObservable();
|
public readonly memoUpdate: Observable<string[]> = this._memoUpdated.asObservable();
|
||||||
|
public readonly priceUpdate: Observable<number> = this._priceUpdated.asObservable();
|
||||||
private UserSub: Subscription = new Subscription();
|
private UserSub: Subscription = new Subscription();
|
||||||
|
|
||||||
constructor(private http: HttpClient, public userService: UserService){
|
constructor(private http: HttpClient, public userService: UserService){
|
||||||
this.getAddr();
|
this.getAddr();
|
||||||
this.getHeight();
|
this.getHeight();
|
||||||
this.getMemos();
|
this.getMemos();
|
||||||
|
this.getPrice();
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeight(){
|
getHeight(){
|
||||||
|
@ -32,9 +35,16 @@ export class FullnodeService{
|
||||||
return obs;
|
return obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
//getHeightUpdateListener() {
|
getPrice(){
|
||||||
//return this.heightUpdated;
|
let obs = this.http.get<{message: string, price: number}>('http://localhost:3000/api/price');
|
||||||
//}
|
obs.subscribe((PriceData) => {
|
||||||
|
this.dataStore.price = PriceData.price;
|
||||||
|
console.log(this.dataStore.price);
|
||||||
|
this._priceUpdated.next(Object.assign({},this.dataStore).price);
|
||||||
|
});
|
||||||
|
|
||||||
|
return obs;
|
||||||
|
}
|
||||||
|
|
||||||
hexToString(hexString: string) {
|
hexToString(hexString: string) {
|
||||||
var str = '';
|
var str = '';
|
||||||
|
@ -81,9 +91,6 @@ export class FullnodeService{
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//getMemoUpdateListener() {
|
|
||||||
//return this.memoUpdated;
|
|
||||||
//}
|
|
||||||
|
|
||||||
getAddr() {
|
getAddr() {
|
||||||
let obs = this.http.get<{message: string, addr: string}>('http://localhost:3000/api/getaddr');
|
let obs = this.http.get<{message: string, addr: string}>('http://localhost:3000/api/getaddr');
|
||||||
|
@ -95,9 +102,4 @@ export class FullnodeService{
|
||||||
|
|
||||||
return obs;
|
return obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
//getAddrUpdateListener() {
|
|
||||||
//return this.addrUpdated;
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
21
src/app/items/item-create/item-create.component.html
Normal file
21
src/app/items/item-create/item-create.component.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<h2 mat-dialog-title>Add item</h2>
|
||||||
|
|
||||||
|
<mat-dialog-content [formGroup]="form">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="Item" formControlName="name">
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<textarea matInput placeholder="Description" formControlName="description"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput type="number" placeholder="Price in USD" formControlName="cost">
|
||||||
|
<div *ngIf="!form.controls['cost'].valid && form.controls['cost'].touched">
|
||||||
|
<div *ngIf="form.controls['cost'].errors?.pattern">Use only numbers</div>
|
||||||
|
</div>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
<mat-dialog-actions>
|
||||||
|
<button mat-raised-button (click)="close()">Close</button>
|
||||||
|
<button mat-raised-button color="primary" (click)="save()">Save</button>
|
||||||
|
</mat-dialog-actions>
|
42
src/app/items/item-create/item-create.component.ts
Normal file
42
src/app/items/item-create/item-create.component.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Inject, Component, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
import { FormBuilder, Validators, FormGroup, FormControl } from '@angular/forms';
|
||||||
|
import { Item } from '../item.model';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-item-create',
|
||||||
|
templateUrl: './item-create.component.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ItemCreateComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
numberRegEx = /\d*\.?\d{1,2}/;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private dialogRef: MatDialogRef<ItemCreateComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) {name, description, cost, user}:Item
|
||||||
|
){
|
||||||
|
this.form = fb.group({
|
||||||
|
name: [null, Validators.required],
|
||||||
|
description: [null, Validators.required],
|
||||||
|
cost: new FormControl('', {
|
||||||
|
validators: [Validators.required, Validators.pattern(this.numberRegEx)],
|
||||||
|
updateOn: "blur"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit () {
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
this.dialogRef.close(this.form.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
}
|
19
src/app/items/item-list/item-list.component.css
Normal file
19
src/app/items/item-list/item-list.component.css
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
* {
|
||||||
|
font-family: 'Roboto-Mono', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer{
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.icon{
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.card{
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.price{
|
||||||
|
margin: 0px;
|
||||||
|
}
|
|
@ -1,10 +1,24 @@
|
||||||
<h1>Items!</h1>
|
<h1>Items!</h1>
|
||||||
<mat-accordion *ngIf="items.length > 0">
|
<div *ngIf="items.length > 0">
|
||||||
<mat-expansion-panel *ngFor="let item of items">
|
<div class="card" *ngFor="let item of items">
|
||||||
<mat-expansion-panel-header>
|
<mat-card>
|
||||||
{{item.name}}
|
<mat-card-title>
|
||||||
</mat-expansion-panel-header>
|
<table cellspacing="0" width="100%">
|
||||||
<p>{{item.description}}</p>
|
<tr>
|
||||||
</mat-expansion-panel>
|
<td>{{item.name}}</td>
|
||||||
</mat-accordion>
|
<td align="right">
|
||||||
|
<p class="price">{{item.cost | currency: 'USD'}}</p>
|
||||||
|
<p class="price"><img class="icon" src="/assets/zec.png" width="12px" />{{priceUpdate | async}}</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</mat-card-title>
|
||||||
|
<mat-card-subtitle>
|
||||||
|
<p>{{item.description}}</p>
|
||||||
|
</mat-card-subtitle>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p *ngIf = "items.length <= 0">No items yet!</p>
|
<p *ngIf = "items.length <= 0">No items yet!</p>
|
||||||
|
<br>
|
||||||
|
<button mat-raised-button (click)="openDialog()">Add item</button>
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { MatDialog, MatDialogConfig} from '@angular/material/dialog';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Item } from '../item.model';
|
import { Item } from '../item.model';
|
||||||
import { Owner } from '../../owner.model';
|
import { Owner } from '../../owner.model';
|
||||||
|
import { FullnodeService } from '../../fullnode.service';
|
||||||
import { UserService } from '../../user.service';
|
import { UserService } from '../../user.service';
|
||||||
import { ItemService } from '../items.service';
|
import { ItemService } from '../items.service';
|
||||||
|
import { ItemCreateComponent } from '../item-create/item-create.component';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-item-list',
|
selector: 'app-item-list',
|
||||||
templateUrl: './item-list.component.html'
|
templateUrl: './item-list.component.html',
|
||||||
|
styleUrls: ['./item-list.component.css']
|
||||||
})
|
})
|
||||||
|
|
||||||
export class ItemListComponent{
|
export class ItemListComponent implements OnInit{
|
||||||
public items: Item[] = [];
|
public items: Item[] = [];
|
||||||
private owner: Owner = {_id: '', name: '', address: ''};
|
private owner: Owner = {_id: '', name: '', address: ''};
|
||||||
public ownerUpdate: Observable<Owner>;
|
public ownerUpdate: Observable<Owner>;
|
||||||
public itemsUpdate: Observable<Item[]>;
|
public itemsUpdate: Observable<Item[]>;
|
||||||
|
public priceUpdate: Observable<number>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
itemService: ItemService,
|
public itemService: ItemService,
|
||||||
userService: UserService
|
userService: UserService,
|
||||||
|
public fullnodeService: FullnodeService,
|
||||||
|
private dialog: MatDialog
|
||||||
) {
|
) {
|
||||||
this.ownerUpdate = userService.ownerUpdate;
|
this.ownerUpdate = userService.ownerUpdate;
|
||||||
this.itemsUpdate = itemService.itemsUpdated;
|
this.itemsUpdate = itemService.itemsUpdated;
|
||||||
|
this.priceUpdate = fullnodeService.priceUpdate;
|
||||||
this.ownerUpdate.subscribe((owner) => {
|
this.ownerUpdate.subscribe((owner) => {
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
itemService.getItems(this.owner.address);
|
itemService.getItems(this.owner.address);
|
||||||
|
@ -34,5 +42,22 @@ export class ItemListComponent{
|
||||||
|
|
||||||
ngOnInit(){
|
ngOnInit(){
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openDialog(){
|
||||||
|
const dialogConfig = new MatDialogConfig();
|
||||||
|
|
||||||
|
dialogConfig.disableClose = true;
|
||||||
|
dialogConfig.autoFocus = true;
|
||||||
|
dialogConfig.data = {name: '' , user: '', description: '', cost: 0}
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open(ItemCreateComponent, dialogConfig);
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((val) => {
|
||||||
|
//TODO connect to Item service
|
||||||
|
var item:Item = {name: val.name, description: val.description, cost: val.cost, user: this.owner.address};
|
||||||
|
this.itemService.addItem(item);
|
||||||
|
this.itemService.getItems(this.owner.address);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: string;
|
_id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
cost: number;
|
cost: number;
|
||||||
|
|
|
@ -25,5 +25,18 @@ export class ItemService{
|
||||||
console.log('No items found');
|
console.log('No items found');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return obs;
|
||||||
|
}
|
||||||
|
|
||||||
|
addItem(item: Item) {
|
||||||
|
const params = new HttpParams().append('item', JSON.stringify(item));
|
||||||
|
let obs = this.http.post<{message: string}>('http://localhost:3000/api/item', { item: item });
|
||||||
|
|
||||||
|
obs.subscribe((ItemResponse) => {
|
||||||
|
console.log('Item added');
|
||||||
|
});
|
||||||
|
|
||||||
|
return obs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
<app-header></app-header>
|
<app-header></app-header>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>{{message}}</h1>
|
<h1>{{message}}</h1>
|
||||||
<br>
|
|
||||||
{{addrUpdate | async}}
|
|
||||||
</div>
|
</div>
|
||||||
<app-item-list></app-item-list>
|
<app-item-list></app-item-list>
|
||||||
|
|
BIN
src/assets/zec.png
Normal file
BIN
src/assets/zec.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
Loading…
Reference in a new issue