feat: notifications
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<main class="main">
|
||||
<app-toast-container></app-toast-container>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
<nav>
|
||||
|
||||
@@ -9,10 +9,11 @@ import {
|
||||
} from '@ng-bootstrap/ng-bootstrap/nav';
|
||||
import { filter } from 'rxjs';
|
||||
import { APIService } from './services/api';
|
||||
import { ToastContainer } from "./components/toast-container/toast-container";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, NgbModule, NgbNav, NgbNavItem, NgbNavItemRole, NgbNavLinkBase, RouterLinkWithHref],
|
||||
imports: [RouterOutlet, NgbModule, NgbNav, NgbNavItem, NgbNavItemRole, NgbNavLinkBase, RouterLinkWithHref, ToastContainer],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.less'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3">
|
||||
@for (notification of notifications.notifications(); track notification) {
|
||||
<ngb-toast
|
||||
[autohide]="true"
|
||||
[delay]="notification.delay ?? 3000"
|
||||
(hidden)="notifications.remove(notification)"
|
||||
>
|
||||
<div class="text-{{ notification.type }}">
|
||||
{{ notification.message }}
|
||||
</div>
|
||||
</ngb-toast>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ToastContainer } from './toast-container';
|
||||
|
||||
describe('ToastContainer', () => {
|
||||
let component: ToastContainer;
|
||||
let fixture: ComponentFixture<ToastContainer>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ToastContainer],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ToastContainer);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
client/src/app/components/toast-container/toast-container.ts
Normal file
13
client/src/app/components/toast-container/toast-container.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { NgbToastModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { NotificationService } from '../../services/notification';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toast-container',
|
||||
imports: [NgbToastModule],
|
||||
templateUrl: './toast-container.html',
|
||||
styleUrl: './toast-container.less',
|
||||
})
|
||||
export class ToastContainer {
|
||||
notifications = inject(NotificationService);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { APIService } from '../../services/api';
|
||||
import { GenericMessage } from '@message/Message';
|
||||
import { NotificationService } from '../../services/notification';
|
||||
|
||||
@Component({
|
||||
selector: 'app-screen-login',
|
||||
@@ -21,6 +22,7 @@ export class ScreenLogin {
|
||||
|
||||
constructor(
|
||||
private api: APIService,
|
||||
private notify: NotificationService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private fb: FormBuilder,
|
||||
@@ -43,7 +45,7 @@ export class ScreenLogin {
|
||||
},
|
||||
error: (resp) => {
|
||||
let msg: GenericMessage = resp.error;
|
||||
this.error.set(msg.message || 'Login failed. Please try again.');
|
||||
this.notify.error(msg.message || 'Login failed. Please try again.');
|
||||
this.loading.set(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<button class="btn btn-primary btn-lg" (click)="sendMoney()">
|
||||
Send Money
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-lg" (click)="cancel()">
|
||||
<button class="btn btn-outline-secondary btn-lg" (click)="clear()">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { APIService } from '../../services/api';
|
||||
import { NotificationService } from '../../services/notification';
|
||||
|
||||
@Component({
|
||||
selector: 'app-screen-send',
|
||||
@@ -15,21 +16,30 @@ export class ScreenSend {
|
||||
|
||||
constructor(
|
||||
private api: APIService,
|
||||
private notify: NotificationService,
|
||||
){}
|
||||
|
||||
sendMoney() {
|
||||
this.api.send(this.amount, this.recipient, this.reference).subscribe({
|
||||
next:()=> {
|
||||
this.cancel()
|
||||
//TODO show success message
|
||||
this.clear()
|
||||
this.notify.success(`Sent ${this.amount} to ${this.recipient}`);
|
||||
},
|
||||
error:()=> {
|
||||
//TODO show error message
|
||||
error:(err)=> {
|
||||
if(err.status == 404){
|
||||
this.notify.error(`Invalid recipient "${this.recipient}"`);
|
||||
}
|
||||
else if(err.status == 402){
|
||||
this.notify.error(`Insufficient funds.`);
|
||||
}
|
||||
else{
|
||||
this.notify.error(`An error occurred during payment: ${err.status}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel() {
|
||||
clear() {
|
||||
this.amount = 0;
|
||||
this.recipient = '';
|
||||
this.reference = '';
|
||||
|
||||
16
client/src/app/services/notification.spec.ts
Normal file
16
client/src/app/services/notification.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationService } from './notification';
|
||||
|
||||
describe('Notification', () => {
|
||||
let service: NotificationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(NotificationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
41
client/src/app/services/notification.ts
Normal file
41
client/src/app/services/notification.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Injectable, signal } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NotificationService {
|
||||
notifications = signal<Notification[]>([]);
|
||||
|
||||
show(notification: Notification) {
|
||||
this.notifications.update(items => [
|
||||
...items,
|
||||
notification,
|
||||
]);
|
||||
}
|
||||
|
||||
remove(notification: Notification) {
|
||||
this.notifications.update(items =>
|
||||
items.filter(item => item !== notification)
|
||||
);
|
||||
}
|
||||
|
||||
success(message: string) {
|
||||
this.show({ type: 'success', message});
|
||||
}
|
||||
|
||||
error(message: string) {
|
||||
this.show({ type: 'danger', message});
|
||||
}
|
||||
|
||||
warn(message: string) {
|
||||
this.show({ type: 'warning', message});
|
||||
}
|
||||
info(message: string) {
|
||||
this.show({ type: 'info', message});
|
||||
}
|
||||
}
|
||||
export interface Notification {
|
||||
type: 'success' | 'warning' | 'info' | 'danger';
|
||||
message: string;
|
||||
delay?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user