Commit 771b9ff0 authored by Dirk Luijk's avatar Dirk Luijk

Added wallets section to frontend

parent 9996d5f5
......@@ -2,3 +2,4 @@ export * from './block';
export * from './blockchain';
export * from './transaction';
export * from './transaction-result';
export * from './wallet';
import { Transaction } from './transaction';
export interface Wallet {
balance: number;
minedTransactions: Transaction[];
unconfirmedTransactions: Transaction[];
}
<div class="container">
<div class="header">
<img src="../assets/images/coin-logo.png" alt="logo" />
<h1>CraftsCoin</h1>
</div>
<div>
<p class="lead">Node interface</p>
</div>
<nav class="mt-5">
<img src="../assets/images/coin-logo.png" alt="logo" class="logo float-left"/>
<h1>CraftsCoin</h1>
<p class="lead">Node interface</p>
<nav class="mt-5">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" [routerLink]="['transactions']" routerLinkActive="active">Pending transactions</a>
......@@ -17,6 +15,9 @@
<li class="nav-item">
<a class="nav-link" [routerLink]="['peers']" routerLinkActive="active">Peers</a>
</li>
<li class="nav-item">
<a class="nav-link" [routerLink]="['wallets']" routerLinkActive="active">Wallets</a>
</li>
</ul>
</nav>
......
......@@ -2,14 +2,8 @@ nav {
margin-bottom: 2rem;
}
.header img {
float: left;
width: 100px;
height: 100px;
}
.header h1 {
position: relative;
top: 18px;
left: 10px;
.logo {
height: 85px;
margin-right: 1rem;
margin-top: 0.5rem;
}
......@@ -14,6 +14,10 @@ import { PendingTransactionsComponent } from './transactions/pending/pending-tra
import { BlockchainService } from './blockchain/blockchain.service';
import { PeersComponent } from './peers/peers.component';
import { PeersService } from './peers/peers.service';
import { WalletComponent } from './wallets/wallet/wallet.component';
import { WalletsComponent } from './wallets/wallets.component';
import { WalletService } from './wallets/wallet.service';
import { BalanceComponent } from './wallets/balance/balance.component';
const routes: Routes = [
{ path: '', redirectTo: '/transactions', pathMatch: 'full' },
......@@ -21,6 +25,13 @@ const routes: Routes = [
{ path: 'transactions/create', component: CreateTransactionComponent },
{ path: 'blockchain', component: BlockchainComponent },
{ path: 'peers', component: PeersComponent },
{
path: 'wallets',
component: WalletsComponent,
children: [
{path: ':name', component: WalletComponent}
]
},
];
@NgModule({
......@@ -31,7 +42,10 @@ const routes: Routes = [
CreateTransactionComponent,
MessagesComponent,
BlockchainComponent,
PeersComponent
PeersComponent,
WalletComponent,
WalletsComponent,
BalanceComponent
],
imports: [
BrowserAnimationsModule,
......@@ -39,7 +53,7 @@ const routes: Routes = [
RouterModule.forRoot(routes, { useHash: true }),
HttpClientModule
],
providers: [TransactionService, MessageService, BlockchainService, PeersService],
providers: [TransactionService, MessageService, BlockchainService, PeersService, WalletService],
bootstrap: [AppComponent]
})
export class AppModule {
......
......@@ -45,6 +45,10 @@ export class BlockchainComponent implements OnInit, OnDestroy {
}
mine(): void {
this.blockchainService.doMine().subscribe(block => this.blockchain.chain.unshift(block));
this.blockchainService.doMine().subscribe(block => {
if (block) {
this.blockchain.chain.unshift(block);
}
});
}
}
......@@ -29,6 +29,10 @@ export class BlockchainService {
return this.http.post<Block>('/api/mine', {})
.do(block => this.messageService.log(`Successfully mined block ${block.index} with ${block.transactions.length} transaction(s)`))
;
.catch(() => {
this.messageService.log(`ERROR while mining!`);
return Observable.empty<Block>();
});
}
}
......@@ -3,15 +3,15 @@
<form #form="ngForm" (submit)="submit()">
<div class="form-group">
<label for="from">From</label>
<input type="text" name="from" [(ngModel)]="newTransaction.from" required class="form-control" id="from" placeholder="From">
<input type="text" name="from" [(ngModel)]="newTransaction.from" required class="form-control form-control-lg" id="from" placeholder="From">
</div>
<div class="form-group">
<label for="to">To</label>
<input type="text" name="to" [(ngModel)]="newTransaction.to" required class="form-control" id="to" placeholder="To">
<input type="text" name="to" [(ngModel)]="newTransaction.to" required class="form-control form-control-lg" id="to" placeholder="To">
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input type="number" name="amount" [(ngModel)]="newTransaction.amount" required class="form-control" id="amount" placeholder="Amount">
<input type="number" name="amount" [(ngModel)]="newTransaction.amount" required class="form-control form-control-lg" id="amount" placeholder="Amount">
</div>
<button class="btn btn-primary" type="submit" [disabled]="form.pristine || form.invalid">Save</button>
......
......@@ -4,8 +4,7 @@ import { Observable } from 'rxjs/Observable';
import { MessageService } from '../messages/message.service';
import { Transaction } from '../api/transaction';
import { TransactionResult } from '../api/transaction-result';
import { Transaction, TransactionResult } from '../api';
import 'rxjs/add/operator/do';
......
{{ currentBalance | currency: 'EUR' }}
import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/bufferCount';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/takeWhile';
@Component({
selector: 'app-balance',
templateUrl: './balance.component.html'
})
export class BalanceComponent implements OnInit, OnChanges {
@Input() balance: number;
currentBalance: number;
private nextBalance = new ReplaySubject<number>();
ngOnInit(): void {
this.nextBalance
.startWith(0)
.bufferCount(2, 1)
.switchMap(([prev, current]) =>
Observable.timer(0, 30)
.map(i => prev + i * (current - prev) / 7)
.takeWhile(i => current > prev ? i < current : i > current)
.concat(Observable.of(current))
)
.subscribe(b => this.currentBalance = b);
}
ngOnChanges(changes: SimpleChanges): void {
this.nextBalance.next(this.balance);
}
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { MessageService } from '../messages/message.service';
import { Wallet } from '../api';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
@Injectable()
export class WalletService {
constructor(private http: HttpClient, private messageService: MessageService) {}
findWallet(name: string): Observable<Wallet> {
this.messageService.log('Fetching wallet...');
return this.http.get<Wallet>(`/api/wallet/${name}`)
.do(() => this.messageService.log(`Wallet found.`))
.catch(() => {
this.messageService.log(`Wallet NOT found!`);
return Observable.empty<Wallet>();
});
}
}
<h3 class="mb-4">Result</h3>
<div class="wallet" *ngIf="wallet">
<h3>
<app-balance class="text-success"
[class.text-danger]="wallet.balance < 0"
[balance]="wallet.balance"></app-balance>
</h3>
<ul class="list-group mt-3">
<li *ngFor="let transaction of wallet.unconfirmedTransactions; trackBy: trackByFn" [@slide]="wallet.unconfirmedTransactions.length"
class="list-group-item list-group-item-light">
<p class="lead mb-0">{{ transaction.id }}</p>
<p class="mb-0">
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-success">{{ transaction.amount | currency: 'EUR' }}</span>
<span class="badge badge-danger float-right">Unconfirmed</span>
</p>
</li>
<li *ngFor="let transaction of wallet.minedTransactions; trackBy: trackByFn" [@slide]="wallet.minedTransactions.length"
class="list-group-item">
<p class="lead mb-0">{{ transaction.id }}</p>
<p class="mb-0">
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-success">{{ transaction.amount | currency: 'EUR' }}</span>
</p>
</li>
</ul>
</div>
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { transition, trigger, useAnimation } from '@angular/animations';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom';
import { WalletService } from '../wallet.service';
import { Transaction, Wallet } from '../../api';
import { slideAnimation } from '../../slide.animation';
@Component({
selector: 'app-wallet',
templateUrl: './wallet.component.html',
styleUrls: ['./wallet.component.scss'],
animations: [
trigger('slide', [
transition('* => *', [
useAnimation(slideAnimation),
]),
]),
],
})
export class WalletComponent implements OnInit, OnDestroy {
wallet: Wallet;
private subscription: Subscription;
constructor(private walletService: WalletService, private route: ActivatedRoute) {}
ngOnInit(): void {
this.subscription = this.route.paramMap
.map(params => params.get('name'))
.switchMap(name => Observable.timer(0, 5000).map(() => name))
.switchMap(name => this.walletService.findWallet(name))
.subscribe(wallet => this.wallet = wallet);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
trackByFn(index: number, transaction: Transaction): string {
return transaction.id;
}
}
<h3 class="mb-4">Search wallet</h3>
<form class="form-inline mb-5" (submit)="search()">
<input type="text" class="form-control form-control-lg mr-sm-2" name="name" [(ngModel)]="name" required placeholder="Typ a name...">
<button type="submit" class="btn btn-primary btn-lg">Search</button>
</form>
<router-outlet></router-outlet>
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-wallets',
templateUrl: './wallets.component.html',
styleUrls: ['./wallets.component.scss']
})
export class WalletsComponent {
name = '';
constructor(private router: Router) {}
search(): void {
this.router.navigate(['/wallets', this.name]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment