fix(login+register+dashboard): adding interceptor handling error, changing interface graphs

This commit is contained in:
Desy Ayurianti 2024-10-25 10:55:52 +07:00
parent fac32f6978
commit ee463ec174
20 changed files with 755 additions and 413 deletions

View File

@ -1,9 +1,10 @@
import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core'; import { ApplicationConfig, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router'; import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations'; import { provideAnimations } from '@angular/platform-browser/animations';
import { ToastrModule } from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { httpErrorInterceptor } from './cores/interceptors/http-error-interceptor.service';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@ -12,5 +13,6 @@ export const appConfig: ApplicationConfig = {
provideHttpClient(), provideHttpClient(),
provideAnimations(), provideAnimations(),
importProvidersFrom(ToastrModule.forRoot()), importProvidersFrom(ToastrModule.forRoot()),
provideHttpClient(withInterceptors([httpErrorInterceptor])) // Register your HttpErrorInterceptor
] ]
}; };

View File

@ -1,7 +1,6 @@
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { DashboardComponent } from './pages/dashboard/dashboard.component'; import { DashboardComponent } from './pages/dashboard/dashboard.component';
import { LayoutsComponent } from './pages/dashboard/layouts/layouts.component'; import { LayoutsComponent } from './pages/dashboard/layouts/layouts.component';
import { GraphComponent } from './pages/dashboard/page/graph/graph.component';
import { AuthComponent } from './pages/auth/auth.component'; import { AuthComponent } from './pages/auth/auth.component';
import { AuthGuard } from './cores/guards/auth.guard'; import { AuthGuard } from './cores/guards/auth.guard';
import { RegisterComponent } from './pages/register/register.component'; import { RegisterComponent } from './pages/register/register.component';
@ -29,11 +28,6 @@ export const routes: Routes = [
component: DashboardComponent, component: DashboardComponent,
canActivate: [AuthGuard] canActivate: [AuthGuard]
}, },
{
path: 'graph',
component: GraphComponent,
canActivate: [AuthGuard]
},
] ]
} }
]; ];

View File

@ -1,16 +1,20 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { StorageService } from '../services/storage.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthGuard implements CanActivate { export class AuthGuard implements CanActivate {
constructor(private router: Router, private toast: ToastrService) {} constructor(
private router: Router,
private toast: ToastrService,
private storageService: StorageService
) {}
canActivate(): boolean { canActivate(): boolean {
const token = localStorage.getItem('accessToken'); const token = this.storageService.getToken();
if (token) { if (token) {
return true; return true;
} else { } else {

View File

@ -0,0 +1,43 @@
import { HttpInterceptorFn } from '@angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
export const httpErrorInterceptor: HttpInterceptorFn = (req, next) => {
const toast = inject(ToastrService);
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
errorMessage = `Client error: ${error.error.message}`;
} else {
switch (error.status) {
case 0:
errorMessage = 'Network error: Please check your internet connection.';
break;
case 401:
errorMessage = 'Unauthorized: Please log in again.';
break;
case 403:
errorMessage = 'Forbidden: You do not have permission to access this resource.';
break;
case 404:
errorMessage = 'Resource not found: The requested resource does not exist.';
break;
case 500:
errorMessage = 'Server error: Please try again later.';
break;
default:
errorMessage = `Unexpected error: ${error.message}`;
}
}
toast.error(errorMessage, 'Error', { timeOut: 3000 });
return throwError(() => new Error(errorMessage));
})
);
};

View File

@ -25,8 +25,6 @@ export interface ApiResponse {
statusCode: number; statusCode: number;
message: string; message: string;
} }
export interface DHTSensor { export interface DHTSensor {
lightIntensity: number; lightIntensity: number;
temperature: number; temperature: number;

View File

@ -1,12 +1,10 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable, tap } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { ApiService } from './api.service'; import { ApiService } from './api.service';
import { LoginData } from '../interface/auth'; import { LoginData } from '../interface/auth';
import { jwtDecode } from 'jwt-decode'; import { jwtDecode } from 'jwt-decode';
import { StorageService } from './storage.service'; import { StorageService } from './storage.service';
import { ToastrService } from 'ngx-toastr';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -16,9 +14,10 @@ export class AuthService extends ApiService {
private logoutUrl = `${this.baseUrl}auth/logout`; private logoutUrl = `${this.baseUrl}auth/logout`;
private registerUrl = `${this.baseUrl}auth/register`; private registerUrl = `${this.baseUrl}auth/register`;
constructor(http: HttpClient, constructor(
http: HttpClient,
private storageService: StorageService, private storageService: StorageService,
private toast: ToastrService) { ) {
super(http); super(http);
} }
@ -44,7 +43,7 @@ export class AuthService extends ApiService {
} }
logout(): Observable<any> { logout(): Observable<any> {
const token = localStorage.getItem('accessToken'); const token = this.storageService.getToken();
const headers = new HttpHeaders({ const headers = new HttpHeaders({
Authorization: `Bearer ${token}` Authorization: `Bearer ${token}`
}); });
@ -54,21 +53,12 @@ export class AuthService extends ApiService {
this.storageService.clearToken(); this.storageService.clearToken();
this.storageService.clearUserData(); this.storageService.clearUserData();
}), }),
catchError(error => {
this.toast.error('Failed to logout');
return error;
})
); );
} }
register(data: any): Observable<any> { register(data: any): Observable<any> {
const headers = new HttpHeaders({}); const headers = new HttpHeaders({});
return this.http.post<any>(this.registerUrl, data, { headers }).pipe( return this.http.post<any>(this.registerUrl, data, { headers });
catchError(error => {
this.toast.error('Failed to register');
return error;
})
);
} }
getUserFullName(): string | null { getUserFullName(): string | null {

View File

@ -18,7 +18,7 @@ export class SensorService extends ApiService {
private getDataUrl = `${this.baseUrl}api/sensor/getData`; private getDataUrl = `${this.baseUrl}api/sensor/getData`;
private getLatestUrl = `${this.baseUrl}api/sensor/getLatest`; private getLatestUrl = `${this.baseUrl}api/sensor/getLatest`;
private getStatusRelay = `${this.baseUrl}api/get-relay`; private getStatusRelay = `${this.baseUrl}api/relay/get-relay`;
private createAuthHeaders(): HttpHeaders { private createAuthHeaders(): HttpHeaders {
const token = this.storageService.getToken(); const token = this.storageService.getToken();
@ -38,7 +38,7 @@ export class SensorService extends ApiService {
return this.http.get<ApiResponse>(this.getDataUrl, { params, headers }).pipe( return this.http.get<ApiResponse>(this.getDataUrl, { params, headers }).pipe(
catchError(error => { catchError(error => {
this.toast.error('Failed to get sensor data, please try again'); // this.toast.error('Failed to get sensor data for graphic, please try again');
return throwError(error); return throwError(error);
}) })
); );
@ -49,7 +49,7 @@ export class SensorService extends ApiService {
return this.http.get<any>(this.getLatestUrl, { headers }).pipe( return this.http.get<any>(this.getLatestUrl, { headers }).pipe(
catchError(error => { catchError(error => {
this.toast.error('Failed to get sensor data, please try again'); // this.toast.error('Failed to get latest sensor data, please try again');
return throwError(error); return throwError(error);
}) })
); );
@ -60,7 +60,7 @@ export class SensorService extends ApiService {
return this.http.get<any>(this.getStatusRelay, { headers }).pipe( return this.http.get<any>(this.getStatusRelay, { headers }).pipe(
catchError(error => { catchError(error => {
this.toast.error('Failed to get relay status, please try again'); // this.toast.error('Failed to get relay status, please try again');
return throwError(error); return throwError(error);
}) })
); );

View File

@ -8,24 +8,52 @@
<div class="col-md-9 col-lg-8 mx-auto"> <div class="col-md-9 col-lg-8 mx-auto">
<h3 class="login-heading mb-4">Welcome back!</h3> <h3 class="login-heading mb-4">Welcome back!</h3>
<form (ngSubmit)="onSubmit()"> <form (ngSubmit)="onSubmit()">
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required> <input type="email"
class="form-control"
[(ngModel)]="email"
name="email"
id="floatingInput"
placeholder="name@example.com"
required>
<label for="floatingInput">Email address</label> <label for="floatingInput">Email address</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="password" class="form-control" [(ngModel)]="password" name="password" id="floatingPassword" placeholder="Password" required> <input [type]="passwordVisible ? 'text' : 'password'"
class="form-control"
[(ngModel)]="password"
name="password"
id="floatingPassword"
placeholder="Password"
required>
<label for="floatingPassword">Password</label> <label for="floatingPassword">Password</label>
<button type="button"
class="btn btn-link position-absolute end-0 me-3"
(click)="togglePasswordVisibility()">
<i class="fa" [ngClass]="passwordVisible ? 'fa-eye' : 'fa-eye-slash'"></i>
</button>
</div> </div>
<div class="form-check mb-3"> <div class="form-check mb-3">
<input class="form-check-input" type="checkbox" [(ngModel)]="rememberMe" name="rememberMe" id="rememberPasswordCheck"> <input class="form-check-input"
<label class="form-check-label" for="rememberPasswordCheck">Remember Me? </label> type="checkbox"
[(ngModel)]="rememberMe"
name="rememberMe"
id="rememberPasswordCheck">
<label class="form-check-label" for="rememberPasswordCheck">Remember me</label>
</div> </div>
<div class="d-grid"> <div class="d-grid">
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2" type="submit">Sign in</button> <button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2"
type="submit"
[disabled]="loading">
<span *ngIf="!loading">Sign in</span>
<span *ngIf="loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
</button>
<div class="text-center"> <div class="text-center">
<a class="small forgot" routerLink= '/register'>Register here</a> <a class="small forgot" routerLink= '/register'>Register here</a>
</div> </div>

View File

@ -27,3 +27,32 @@
.forgot { .forgot {
color: #16423C; color: #16423C;
} }
.form-floating {
position: relative;
.btn-link {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
border: none;
background: transparent;
cursor: pointer;
color: #16423C;
&:hover {
color: #16423C;
}
}
}
.form-check-input:checked {
background-color: #16423C;
border-color: #16423C;
}
.form-check-input:checked::before {
color: white;
}

View File

@ -5,11 +5,13 @@ import { StorageService } from '../../cores/services/storage.service';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { LoginData } from '../../cores/interface/auth'; import { LoginData } from '../../cores/interface/auth';
import { ToastrService } from 'ngx-toastr'; import { ToastrService } from 'ngx-toastr';
import { CommonModule } from '@angular/common';
import { catchError } from 'rxjs';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
standalone: true, standalone: true,
imports: [FormsModule, RouterModule], imports: [FormsModule, RouterModule, CommonModule],
templateUrl: './auth.component.html', templateUrl: './auth.component.html',
styleUrls: ['./auth.component.scss'] styleUrls: ['./auth.component.scss']
}) })
@ -17,10 +19,22 @@ export class AuthComponent {
email: string = ''; email: string = '';
password: string = ''; password: string = '';
rememberMe: boolean = false; rememberMe: boolean = false;
loading: boolean = false;
passwordVisible: boolean = false;
constructor(private authService: AuthService, private storageService: StorageService, private router: Router, private toastr: ToastrService) {} constructor(private authService: AuthService,
private storageService: StorageService,
private router: Router,
private toast: ToastrService) {}
onSubmit() { onSubmit() {
this.loading = true;
if (!this.email || !this.password) {
this.loading = false;
this.toast.error('Please fill in all fields.');
return;
}
const loginData: LoginData = { const loginData: LoginData = {
email: this.email, email: this.email,
password: this.password, password: this.password,
@ -31,11 +45,17 @@ export class AuthComponent {
(response) => { (response) => {
this.storageService.saveToken(response.data.token); this.storageService.saveToken(response.data.token);
this.router.navigate(['/dashboard']); this.router.navigate(['/dashboard']);
this.toastr.success('Login successful'); this.toast.success('Login successful');
this.loading = false;
}, },
(error) => { (error) => {
this.toastr.error(error.error.message); this.loading = false;
this.toast.error(error.error.message);
} }
); );
} }
togglePasswordVisibility() {
this.passwordVisible = !this.passwordVisible; // Toggle password visibility
}
} }

View File

@ -1,6 +1,6 @@
<div class="container"> <div class="container">
<div> <div>
<h1 class="title">Hello there</h1> <h1 class="title">{{ greeting }}</h1>
<h3 class="description">Welcome back to your management system</h3> <h3 class="description">Welcome back to your management system</h3>
</div> </div>
<div> <div>
@ -8,38 +8,48 @@
</div> </div>
<div> <div>
<button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">BHT</button> <button [ngClass]="{'active-button': selectedButton === 'dht'}" (click)="selectSensor('dht')">DHT</button>
<button [ngClass]="{'active-button': selectedButton === 'npk1'}" (click)="selectSensor('npk1')">NPK 1</button> <button [ngClass]="{'active-button': selectedButton === 'npk1'}" (click)="selectSensor('npk1')">NPK 1</button>
<button [ngClass]="{'active-button': selectedButton === 'npk2'}" (click)="selectSensor('npk2')">NPK 2</button> <button [ngClass]="{'active-button': selectedButton === 'npk2'}" (click)="selectSensor('npk2')">NPK 2</button>
<button [ngClass]="{'active-button': selectedButton === 'relay'}" (click)="selectSensor('relay')">Relay</button> <button [ngClass]="{'active-button': selectedButton === 'relay'}" (click)="selectSensor('relay')">Relay</button>
</div> </div>
<div *ngIf="isLoaded" class="loading"> <div *ngIf="isLoaded" class="loading">
Loading... <i class="fa fa-spinner fa-spin"></i>
</div> </div>
<div *ngIf="!isLoaded && selectedButton === 'dht'" class="card-container"> <ng-template #noData>
<div class="loading">No available data</div>
</ng-template>
<div *ngIf="!isLoaded && selectedButton === 'dht'">
<div *ngIf="sensorData.dht.lightIntensity || sensorData.dht.temperature || sensorData.dht.humidity; else noData">
<div class="card-container">
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.dht.lightIntensity}} Lux</h3> <h3>{{sensorData.dht.lightIntensity}} Lux</h3>
<h6>Light Intensity</h6> <h6>Cahaya</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.dht.temperature}} °C</h3> <h3>{{sensorData.dht.temperature}} °C</h3>
<h6>Temperature</h6> <h6>Temperatur</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.dht.humidity}} %RH</h3> <h3>{{sensorData.dht.humidity}} %RH</h3>
<h6>Humidity</h6> <h6>Kelembaban Udara</h6>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="!isLoaded && selectedButton === 'npk1'" class="card-container"> <div *ngIf="!isLoaded && selectedButton === 'npk1'">
<div *ngIf="sensorData.npk1.temperature || sensorData.npk1.moisture || sensorData.npk1.conductivity; else noData">
<div class="card-container">
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.npk1.temperature}} °C</h3> <h3>{{sensorData.npk1.temperature}} °C</h3>
@ -49,7 +59,7 @@
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.npk1.moisture}} %RH</h3> <h3>{{sensorData.npk1.moisture}} %RH</h3>
<h6>Moisture</h6> <h6>Kelembaban Tanah</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <div class="card-parameter">
@ -58,33 +68,14 @@
<h6>Conductivity</h6> <h6>Conductivity</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <!-- Additional parameters for NPK1... -->
<div>
<h3>{{sensorData.npk1.ph}}</h3>
<h6>pH</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{sensorData.npk1.nitrogen}} PPM</h3>
<h6>Nitrogen</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{sensorData.npk1.phosphorus}} PPM</h3>
<h6>Phosphorus</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{sensorData.npk1.potassium}} PPM</h3>
<h6>Potassium</h6>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="!isLoaded && selectedButton === 'npk2'" class="card-container"> <div *ngIf="!isLoaded && selectedButton === 'npk2'">
<div *ngIf="sensorData.npk2.temperature || sensorData.npk2.moisture || sensorData.npk2.conductivity; else noData">
<div class="card-container">
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{sensorData.npk2.temperature}} °C</h3> <h3>{{sensorData.npk2.temperature}} °C</h3>
@ -93,44 +84,25 @@
</div> </div>
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{ sensorData.npk1.moisture }} %RH</h3> <h3>{{sensorData.npk2.moisture}} %RH</h3>
<h6>Moisture</h6> <h6>Kelembaban Tanah</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <div class="card-parameter">
<div> <div>
<h3>{{ sensorData.npk1.conductivity }} μS/cm</h3> <h3>{{sensorData.npk2.conductivity}} μS/cm</h3>
<h6>Conductivity</h6> <h6>Conductivity</h6>
</div> </div>
</div> </div>
<div class="card-parameter"> <!-- Additional parameters for NPK2... -->
<div>
<h3>{{ sensorData.npk1.ph }}</h3>
<h6>pH</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{ sensorData.npk1.nitrogen}} PPM</h3>
<h6>Nitrogen</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{ sensorData.npk2.phosphorus}} PPM</h3>
<h6>Phosphorus</h6>
</div>
</div>
<div class="card-parameter">
<div>
<h3>{{ sensorData.npk2.potassium }} PPM</h3>
<h6>Potassium</h6>
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="!isLoaded && selectedButton === 'relay'" class="card-container"> <div *ngIf="!isLoaded && selectedButton === 'relay'">
<div class="card-parameter" *ngFor="let relay of relayStatus;"> <div *ngIf="relayStatus.length > 0; else noData">
<div class="card-container" *ngFor="let relay of relayStatus;">
<div class="card-parameter">
<div> <div>
<h3 [ngClass]="relay.current_status ? 'status-on' : 'status-off'"> <h3 [ngClass]="relay.current_status ? 'status-on' : 'status-off'">
{{ relay.current_status ? 'ON' : 'OFF' }} {{ relay.current_status ? 'ON' : 'OFF' }}
@ -139,12 +111,14 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div class="graph"> <div class="graph">
<div class="title-graph">Monitoring</div> <div class="title-graph">Monitoring Graphs</div>
<div class="graph"> <div class="graph">
<app-graph></app-graph> <app-graph></app-graph>
</div> </div>
</div> </div>
</div> </div>

View File

@ -97,7 +97,6 @@ button {
.loading{ .loading{
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
color: #888;
} }
.status-on { .status-on {
@ -107,3 +106,7 @@ button {
.status-off { .status-off {
color: rgb(144, 6, 6); color: rgb(144, 6, 6);
} }
.spinner {
color: #16423C
}

View File

@ -5,7 +5,7 @@ import { GraphComponent } from './page/graph/graph.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SensorService } from '../../cores/services/sensor.service'; import { SensorService } from '../../cores/services/sensor.service';
import { SensorData, StatusRelay } from '../../cores/interface/sensor-data'; import { SensorData, StatusRelay } from '../../cores/interface/sensor-data';
import { interval } from 'rxjs'; import { ToastrService } from 'ngx-toastr';
@Component({ @Component({
selector: 'app-dashboard', selector: 'app-dashboard',
@ -16,9 +16,12 @@ import { interval } from 'rxjs';
}) })
export class DashboardComponent implements OnInit { export class DashboardComponent implements OnInit {
isLoaded: boolean = false; isLoaded: boolean = false;
hasError: boolean = false;
noData: boolean = false;
selectedButton: string = ''; selectedButton: string = '';
latestUpdate: string = ''; latestUpdate: string = '';
intervalId: any; intervalId: any;
greeting: string = '';
sensorData: SensorData = { sensorData: SensorData = {
dht: { lightIntensity: 0, temperature: 0, humidity: 0 }, dht: { lightIntensity: 0, temperature: 0, humidity: 0 },
npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 }, npk1: { temperature: 0, moisture: 0, conductivity: 0, ph: 0, nitrogen: 0, phosphorus: 0, potassium: 0 },
@ -26,11 +29,12 @@ export class DashboardComponent implements OnInit {
}; };
relayStatus: StatusRelay[] = []; relayStatus: StatusRelay[] = [];
constructor(private apiService: SensorService) {} constructor(private apiService: SensorService, private toast: ToastrService) {}
ngOnInit(): void { ngOnInit(): void {
this.selectedButton = 'dht'; this.selectedButton = 'dht';
this.startClock(); this.startClock();
this.updateGreeting();
this.loadData(); this.loadData();
} }
@ -61,6 +65,16 @@ export class DashboardComponent implements OnInit {
this.latestUpdate = now.toLocaleString('en-GB', options); this.latestUpdate = now.toLocaleString('en-GB', options);
} }
updateGreeting(): void {
const hour = new Date().getHours();
if (hour < 12) {
this.greeting = 'Good Morning';
} else if (hour < 18) {
this.greeting = 'Good Afternoon';
} else {
this.greeting = 'Good Evening';
}
}
selectSensor(param: string): void { selectSensor(param: string): void {
this.selectedButton = param; this.selectedButton = param;
@ -73,11 +87,19 @@ export class DashboardComponent implements OnInit {
loadData(): void { loadData(): void {
this.isLoaded = true; this.isLoaded = true;
this.hasError = false;
this.noData = false;
this.apiService.getLatestData().subscribe( this.apiService.getLatestData().subscribe(
(response) => { (response) => {
const data = response.data; const data = response.data;
console.log('Data:', data); console.log('Data:', data);
if ((!data.dht || data.dht.length === 0) &&
(!data.npk1 || data.npk1.length === 0) &&
(!data.npk2 || data.npk2.length === 0)) {
this.noData = true;
} else {
if (data.dht && data.dht.length > 0) { if (data.dht && data.dht.length > 0) {
this.sensorData.dht = { this.sensorData.dht = {
lightIntensity: data.dht[0].viciluminosity ?? 0, lightIntensity: data.dht[0].viciluminosity ?? 0,
@ -109,19 +131,21 @@ export class DashboardComponent implements OnInit {
potassium: data.npk2[0].soilpotassium ?? 0 potassium: data.npk2[0].soilpotassium ?? 0
}; };
} }
}
this.updateLatestTime(); this.updateLatestTime();
this.isLoaded = false; this.isLoaded = false;
}, },
(error) => { (error) => {
console.error('Error fetching sensor data:', error);
this.isLoaded = false; this.isLoaded = false;
this.handleError(error);
} }
); );
} }
loadRelayData(): void { loadRelayData(): void {
this.isLoaded = true; this.isLoaded = true;
this.hasError = false;
this.apiService.getRelayStatus().subscribe( this.apiService.getRelayStatus().subscribe(
(response) => { (response) => {
@ -134,16 +158,26 @@ export class DashboardComponent implements OnInit {
created_at: relay.created_at, created_at: relay.created_at,
current_status: relay.current_status current_status: relay.current_status
})); }));
this.relayStatus.sort((a, b) => a.number - b.number);
} }
console.log('Relay Data:', response);
this.isLoaded = false; this.isLoaded = false;
}, },
(error) => { (error) => {
console.error('Error fetching relay data:', error); this.toast.error('Failed to get relay status, please try again');
this.hasError = true;
this.isLoaded = false; this.isLoaded = false;
} }
); );
} }
handleError(error: any): void {
if (this.selectedButton === 'dht') {
this.toast.error('Error fetching DHT sensor data. Please try again.', 'Error', { timeOut: 3000 });
} else if (this.selectedButton === 'npk1') {
this.toast.error('Error fetching NPK1 sensor data. Please try again.', 'Error', { timeOut: 3000 });
} else if (this.selectedButton === 'npk2') {
this.toast.error('Error fetching NPK2 sensor data. Please try again.', 'Error', { timeOut: 3000 });
}
this.hasError = true;
}
} }

View File

@ -1,17 +1,58 @@
<div class="container-graph"> <div class="container-graph">
<select class="form-select" id="sensorSelect" (change)="onSensorChange($event)"> <ng-container *ngIf="allNoData; else graphContent">
<option value="dht">BHT</option> <div class="no-data">No available data</div>
<option value="npk1">NPK 1</option> </ng-container>
<option value="npk2">NPK 2</option>
</select>
<select class="form-select" id="parameterSelect" (change)="updateChart()" [(ngModel)]="selectedParameter"> <ng-template #graphContent>
<option *ngFor="let parameter of parameters" [value]="getParameterKey(parameter)">{{ parameter }}</option> <div class="sensor-wrapper">
</select> <div class="title">Sensor BHT</div>
<ng-container *ngIf="isLoadingDHT; else dhtData">
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
<i class="fa fa-spinner fa-spin"></i>
</div>
</ng-container>
<ng-template #dhtData>
<canvas #myChartDHT id="myChartDHT" width="600" height="300" style="height: 300px;"></canvas>
<p *ngIf="isNoDataDHT" class="no-data">No available data</p>
</ng-template>
</div> </div>
<div *ngIf="isLoading" class="loading"> <div class="sensor-wrapper">
Loading... <div class="title-with-dropdown">
<div class="title">Sensor NPK 1</div>
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK1" (change)="updateCharts()">
<option value="npk">NPK</option>
<option value="others">Lainnya</option>
</select>
</div>
<ng-container *ngIf="isLoadingNPK1; else npk1Data">
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px">
<i class="fa fa-spinner fa-spin"></i>
</div>
</ng-container>
<ng-template #npk1Data>
<canvas #myChartNPK1 id="myChartNPK1"></canvas>
<p *ngIf="isNoDataNPK1" class="no-data">No available data</p>
</ng-template>
</div> </div>
<canvas #myChart id="myChart"></canvas> <div class="sensor-wrapper">
<div class="title-with-dropdown">
<div class="title">Sensor NPK 2</div>
<select class="form-select" style="margin-top: 10px" [(ngModel)]="selectedNPK2" (change)="updateCharts()">
<option value="npk">NPK</option>
<option value="others">Lainnya</option>
</select>
</div>
<ng-container *ngIf="isLoadingNPK2; else npk2Data">
<div class="d-flex align-items-center" style="padding: 50px 0px 50px 0px;">
<i class="fa fa-spinner fa-spin"></i>
</div>
</ng-container>
<ng-template #npk2Data>
<canvas #myChartNPK2 id="myChartNPK2"></canvas>
<p *ngIf="isNoDataNPK2" class="no-data">No available data</p>
</ng-template>
</div>
</ng-template>
</div>

View File

@ -1,54 +1,74 @@
.chart-container { .container-graph {
width: 100%; display: flex;
max-width: 800px; flex-direction: column;
margin: auto; justify-content: flex-start;
height: max-content;
.sensor-wrapper {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 35px;
background-color: #f1f1f1;
border-radius: 10px;
h2 {
font-size: 18px;
margin-bottom: 10px;
}
}
.title-with-dropdown {
display: flex;
align-items: center;
justify-content: center;
width: 50%;
.title {
flex: 0 0 25%;
text-align: center; text-align: center;
} }
canvas {
width: 100%;
height: 30vh;
margin: 20px;
align-items: center;
}
.form-select { .form-select {
width: max-content; flex: 0 0 25%;
margin-top: 20px; margin: 18px 0px 0px 15px;
display: flexbox; }
flex-direction: column; }
margin-right: 20px;
} }
.container-graph{ .title {
display: flex; text-align: center;
flex-wrap: wrap; font-size: 20px;
justify-content: left; margin: 18px 0px 0px 0px;
height: max-content;
} }
canvas {
height: 500px !important;
width: 100%;
max-width: 2000px;
padding-bottom: 20px;
padding-top: 20px;
}
@media (max-width: 768px) { @media (max-width: 768px) {
canvas { canvas {
display: flex; height: 300px !important;
width: max-content;
flex: 1 1 45%;
}
}
@media (max-width: 576px) {
canvas{
display: flex;
width: max-content;
flex: 1 1 100%;
} }
} }
.loading { .loading {
font-size: 18px; font-size: 18px;
text-align: center; text-align: center;
color: #888;
} }
.spinner {
color: #16423C
}
.no-data {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 18px;
}

View File

@ -6,17 +6,18 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
Chart.register(...registerables); Chart.register(...registerables);
const parameterColors: { [key: string]: string } = { const parameterColors: { [key: string]: string } = {
vicitemperature: '#8F5A62', vicitemperature: '#8F5A62',
vicihumidity: '#16423C', vicihumidity: '#16423C',
viciluminosity: '#DF9B55', viciluminosity: '#DF9B55',
soiltemperature: '#8F5A62', soiltemperature: '#FF6347',
soilhumidity: '#54909c', soilhumidity: '#00BFFF',
soilconductivity: '#661311', soilconductivity: '#A52A2A',
soilph: '#664735', soilph: '#228B22',
soilnitrogen: '#3a6635', soilnitrogen: '#FEDC56',
soilphosphorus: '#3f3566', soilphosphorus: '#B80F0A',
soilpotassium: '#5f3566', soilpotassium: '#6F2DA8',
}; };
@Component({ @Component({
@ -27,11 +28,19 @@ const parameterColors: { [key: string]: string } = {
styleUrls: ['./graph.component.scss'] styleUrls: ['./graph.component.scss']
}) })
export class GraphComponent implements OnInit, AfterViewInit, OnDestroy { export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('myChart', { static: false }) chartElement!: ElementRef<HTMLCanvasElement>; @ViewChild('myChartDHT', { static: false }) dhtChartElement!: ElementRef<HTMLCanvasElement>;
selectedSensor: string = ''; @ViewChild('myChartNPK1', { static: false }) npk1ChartElement!: ElementRef<HTMLCanvasElement>;
selectedParameter: string = ''; @ViewChild('myChartNPK2', { static: false }) npk2ChartElement!: ElementRef<HTMLCanvasElement>;
parameters: string[] = [];
isLoading: boolean = true; isLoadingDHT: boolean = true;
isLoadingNPK1: boolean = true;
isLoadingNPK2: boolean = true;
isNoDataDHT: boolean = false;
isNoDataNPK1: boolean = false;
isNoDataNPK2: boolean = false;
allNoData: boolean = false;
sensorParameters: { [key: string]: string[] } = { sensorParameters: { [key: string]: string[] } = {
dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'], dht: ['vicitemperature', 'vicihumidity', 'viciluminosity'],
@ -47,28 +56,32 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
soilhumidity: 'Soil Humidity (%)', soilhumidity: 'Soil Humidity (%)',
soilconductivity: 'Conductivity (μS/cm)', soilconductivity: 'Conductivity (μS/cm)',
soilph: 'pH', soilph: 'pH',
soilnitrogen: 'Nitrogen (PPM)', soilnitrogen: 'Nitrogen (mg/l)',
soilphosphorus: 'Phosphorus (PPM)', soilphosphorus: 'Phosphorus (mg/l)',
soilpotassium: 'Potassium (PPM)', soilpotassium: 'Potassium (mg/l)',
}; };
chart: Chart | undefined; charts: { [key: string]: Chart | undefined } = {
labelsHourly: string[] = []; dht: undefined,
npk1: undefined,
npk2: undefined,
};
selectedNPK1: string = 'npk';
selectedNPK2: string = 'npk';
private resizeListener!: () => void; private resizeListener!: () => void;
constructor(private sensorService: SensorService) {} constructor(private sensorService: SensorService) {}
ngOnInit(): void { ngOnInit(): void {
this.selectedSensor = 'dht';
this.updateParameters();
this.selectedParameter = 'vicitemperature';
this.updateChart();
this.resizeListener = this.onResize.bind(this); this.resizeListener = this.onResize.bind(this);
window.addEventListener('resize', this.resizeListener); window.addEventListener('resize', this.resizeListener);
this.updateCharts();
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.updateChart(); this.onResize();
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -76,10 +89,202 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
} }
onResize(): void { onResize(): void {
if (this.chart) { Object.values(this.charts).forEach(chart => {
this.chart.destroy(); if (chart) {
this.updateChart(); chart.destroy();
} }
});
this.updateCharts();
}
updateCharts(): void {
this.isLoadingDHT = this.isLoadingNPK1 = this.isLoadingNPK2 = true;
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const startEnd = `${year}-${month}-${day}`;
// const startEnd = '2024-10-24';
const timeRange = 'HOURLY';
// Fetch data for DHT
this.sensorService.getSensorData('dht', '', startEnd, timeRange).subscribe({
next: (response) => {
if (response.statusCode === 200 && response.data.dht?.length > 0) {
this.createChart(this.dhtChartElement.nativeElement, response, 'dht', 'npk');
this.isNoDataDHT = false;
this.allNoData = false;
} else {
this.isNoDataDHT = true;
}
this.isLoadingDHT = false;
},
error: () => {
this.isLoadingDHT = false;
this.isNoDataDHT = true;
}
});
// Fetch data for NPK1
this.sensorService.getSensorData('npk1', '', startEnd, timeRange).subscribe({
next: (response) => {
if (response.statusCode === 200 && response.data.npk1?.length > 0) {
this.createChart(this.npk1ChartElement.nativeElement, response, 'npk1', this.selectedNPK1);
this.isNoDataNPK1 = false;
this.allNoData = false;
} else {
this.isNoDataNPK1 = true;
}
this.isLoadingNPK1 = false;
},
error: () => {
this.isLoadingNPK1 = false;
this.isNoDataNPK1 = true;
}
});
// Fetch data for NPK2
this.sensorService.getSensorData('npk2', '', startEnd, timeRange).subscribe({
next: (response) => {
if (response.statusCode === 200 && response.data.npk2?.length > 0) {
this.createChart(this.npk2ChartElement.nativeElement, response, 'npk2', this.selectedNPK2);
this.isNoDataNPK2 = false;
this.allNoData = false;
} else {
this.isNoDataNPK2 = true;
}
this.isLoadingNPK2 = false;
},
error: () => {
this.isLoadingNPK2 = false;
this.isNoDataNPK2 = true;
}
});
}
createChart(canvas: HTMLCanvasElement, response: ApiResponse, sensor: string, selectedOption: string): void {
const ctx = canvas.getContext('2d');
const parameters = this.sensorParameters[sensor];
if (!ctx) {
console.error('Failed to get canvas context for sensor:', sensor);
return;
}
let datasets: any[] = [];
if (sensor === 'dht') {
// Handle DHT parameters directly
datasets = ['vicitemperature', 'vicihumidity', 'viciluminosity'].map(parameter => {
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
if (data.length === 0) {
console.warn(`No data found for parameter: ${parameter}`);
return null;
}
const displayName = this.parameterDisplayNames[parameter] || parameter;
const borderColor = parameterColors[parameter] || '#000000';
const backgroundColor = `${borderColor}4D`;
return {
label: displayName,
data,
borderColor,
borderWidth: 1.5,
fill: true,
backgroundColor,
tension: 0.5,
pointRadius: 0,
pointHoverRadius: 0,
};
}).filter(dataset => dataset !== null);
} else {
// Handle NPK1 and NPK2 as before
datasets = parameters.map(parameter => {
const { data, labels } = this.getDataFromResponse(response, sensor, parameter);
if (data.length === 0) {
console.warn(`No data found for parameter: ${parameter}`);
return null;
}
const displayName = this.parameterDisplayNames[parameter] || parameter;
const borderColor = parameterColors[parameter] || '#000000';
const backgroundColor = `${borderColor}4D`;
// Filter datasets based on the selected option
if (selectedOption === 'npk' && ['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
return {
label: displayName,
data,
borderColor,
borderWidth: 1.5,
fill: true,
backgroundColor,
tension: 0.5,
pointRadius: 0,
pointHoverRadius: 0,
};
} else if (selectedOption === 'others' && !['soilphosphorus', 'soilnitrogen', 'soilpotassium'].includes(parameter)) {
return {
label: displayName,
data,
borderColor,
borderWidth: 1.5,
fill: true,
backgroundColor,
tension: 0.5,
pointRadius: 0,
pointHoverRadius: 0,
};
}
return null;
}).filter(dataset => dataset !== null);
}
if (datasets.length === 0) {
console.warn('No valid datasets to render for sensor:', sensor);
return;
}
if (this.charts[sensor]) {
this.charts[sensor]?.destroy();
}
this.charts[sensor] = new Chart(ctx, {
type: 'line',
data: {
labels: this.getLabels(response, sensor),
datasets,
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
enabled: true,
mode: 'nearest',
intersect: false,
callbacks: {
label: (tooltipItem) => {
const paramLabel = tooltipItem.dataset.label;
const value = tooltipItem.formattedValue;
return `${paramLabel}: ${value}`;
}
}
},
legend: { display: true }
},
scales: {
x: { grid: { display: false }, beginAtZero: true },
y: { grid: { display: false }, beginAtZero: true }
}
}
});
} }
getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } { getDataFromResponse(response: ApiResponse, sensor: string, parameter: string): { data: number[], labels: string[] } {
@ -100,112 +305,8 @@ export class GraphComponent implements OnInit, AfterViewInit, OnDestroy {
return { data, labels }; return { data, labels };
} }
getLabels(response: ApiResponse, sensor: string): string[] {
createChart(data: number[], parameter: string, labels: string[]): void { const sensorData = response.data[sensor as keyof typeof response.data];
const canvas = this.chartElement.nativeElement; return sensorData ? sensorData.map(item => `${item.hour}.00`) : [];
const ctx = canvas.getContext('2d');
if (this.chart) {
this.chart.destroy();
}
if (ctx) {
const displayName = this.parameterDisplayNames[parameter] || parameter;
const borderColor = parameterColors[parameter] || '#000000';
const backgroundColor = `${borderColor}4D`;
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: displayName,
data,
borderColor,
borderWidth: 1.5,
fill: true,
backgroundColor,
tension: 0.5,
pointRadius: 0,
pointHoverRadius: 0,
pointBackgroundColor: borderColor,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
enabled: true,
mode: 'nearest',
intersect: false,
callbacks: {
label: (tooltipItem) => {
const paramLabel = displayName;
const value = tooltipItem.formattedValue;
return `${paramLabel}: ${value}`;
}
}
},
legend: { display: false }
},
scales: {
x: { grid: { display: false }, beginAtZero: true },
y: { grid: { display: false }, beginAtZero: true }
}
}
});
}
}
getParameterKey(displayName: string): string {
return Object.keys(this.parameterDisplayNames).find(key => this.parameterDisplayNames[key] === displayName) || '';
}
onSensorChange(event: Event): void {
const select = event.target as HTMLSelectElement;
this.selectedSensor = select.value;
this.updateParameters();
if (this.selectedSensor === 'dht') {
this.selectedParameter = 'vicitemperature';
} else if (this.selectedSensor === 'npk1' || this.selectedSensor === 'npk2') {
this.selectedParameter = 'soiltemperature';
}
this.updateChart();
}
updateParameters(): void {
this.parameters = this.sensorParameters[this.selectedSensor].map(param => this.parameterDisplayNames[param]);
this.selectedParameter = this.parameters[1];
}
updateChart(): void {
this.isLoading = true;
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
const startEnd= `${year}-${month}-${day}`;
const timeRange = 'HOURLY';
this.sensorService.getSensorData(this.selectedSensor, this.selectedParameter, startEnd, timeRange).subscribe(
(response: ApiResponse) => {
if (response.statusCode === 200) {
const { data, labels } = this.getDataFromResponse(response, this.selectedSensor, this.selectedParameter);
this.createChart(data, this.selectedParameter, labels);
} else {
console.error('Error fetching data:', response.message);
}
this.isLoading = false;
},
(error) => {
console.error('API Error:', error);
this.isLoading = false;
}
);
} }
} }

View File

@ -10,25 +10,44 @@
<form (ngSubmit)="onSubmit()"> <form (ngSubmit)="onSubmit()">
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="text" class="form-control" [(ngModel)]="fullname" name="fullname" id="floatingFullname" placeholder="Full Name" required> <input type="text" class="form-control" [(ngModel)]="fullname" name="fullname" id="floatingFullname" placeholder="Full Name" required autocomplete="off">
<label for="floatingFullname">Full Name</label> <label for="floatingFullname">Full Name</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="text" class="form-control" [(ngModel)]="username" name="username" id="floatingUsername" placeholder="Username" required> <input type="text" class="form-control" [(ngModel)]="username" name="username" id="floatingUsername" placeholder="Username" required autocomplete="off">
<label for="floatingUsername">Username</label> <label for="floatingUsername">Username</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required> <input type="email" class="form-control" [(ngModel)]="email" name="email" id="floatingInput" placeholder="name@example.com" required autocomplete="off">
<label for="floatingInput">Email address</label> <label for="floatingInput">Email address</label>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input type="password" class="form-control" [(ngModel)]="password" name="password" id="floatingPassword" placeholder="Password" required> <input [type]="passwordVisible ? 'text' : 'password'"
class="form-control"
[(ngModel)]="password"
name="password"
id="floatingPassword"
placeholder="Password"
required autocomplete="off">
<label for="floatingPassword">Password</label> <label for="floatingPassword">Password</label>
<button type="button"
class="btn btn-link position-absolute end-0 me-3"
(click)="togglePasswordVisibility()">
<i class="fa" [ngClass]="passwordVisible ? 'fa-eye' : 'fa-eye-slash'"></i>
</button>
</div> </div>
<div class="d-grid"> <div class="d-grid">
<button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2" type="submit">Sign Up</button> <button class="btn btn-lg color-btn btn-login text-uppercase fw-bold mb-2"
type="submit"
[disabled]="loading">
<span *ngIf="!loading">Sign up</span>
<span *ngIf="loading">
<i class="fa fa-spinner fa-spin"></i>
</span>
</button>
<div class="text-center"> <div class="text-center">
<a class="small forgot" routerLink= '/auth'>Log In</a> <a class="small forgot" routerLink='/auth'>Sign In here</a>
</div> </div>
</div> </div>
</form> </form>

View File

@ -27,3 +27,22 @@
.forgot{ .forgot{
color: #16423C; color: #16423C;
} }
.form-floating {
position: relative;
.btn-link {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
border: none;
background: transparent;
cursor: pointer;
color: #16423C;
&:hover {
color: #16423C;
}
}
}

View File

@ -1,4 +1,4 @@
import { Component } from '@angular/core'; import { Component, OnInit } from '@angular/core'; // Import OnInit
import { Router, RouterModule } from '@angular/router'; import { Router, RouterModule } from '@angular/router';
import { AuthService } from '../../cores/services/auth.service'; import { AuthService } from '../../cores/services/auth.service';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@ -13,15 +13,35 @@ import { CommonModule } from '@angular/common';
templateUrl: './register.component.html', templateUrl: './register.component.html',
styleUrls: ['./register.component.scss'] styleUrls: ['./register.component.scss']
}) })
export class RegisterComponent { export class RegisterComponent implements OnInit {
username: string = ''; username: string = '';
password: string = ''; password: string = '';
email: string = ''; email: string = '';
fullname: string = ''; fullname: string = '';
loading: boolean = false;
passwordVisible: boolean = false;
constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {} constructor(private authService: AuthService, private router: Router, private toast: ToastrService) {}
ngOnInit(): void {
this.username = '';
this.password = '';
this.email = '';
this.fullname = '';
}
togglePasswordVisibility() {
this.passwordVisible = !this.passwordVisible;
}
onSubmit() { onSubmit() {
this.loading = true;
if (!this.username || !this.password || !this.email || !this.fullname) {
this.loading = false;
this.toast.error('Please fill in all fields.');
return;
}
const registrationData: RegistrationData = { const registrationData: RegistrationData = {
username: this.username, username: this.username,
pwd: this.password, pwd: this.password,
@ -33,10 +53,12 @@ export class RegisterComponent {
this.authService.register(registrationData).subscribe( this.authService.register(registrationData).subscribe(
(response) => { (response) => {
this.loading = false;
this.toast.success('Registration successful'); this.toast.success('Registration successful');
this.router.navigate(['/auth']); this.router.navigate(['/auth']);
}, },
(error) => { (error) => {
this.loading = false;
this.toast.error(error.error.message); this.toast.error(error.error.message);
} }
); );

View File

@ -17,6 +17,7 @@
<script src="https://code.highcharts.com/modules/exporting.js"></script> <script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/modules/export-data.js"></script> <script src="https://code.highcharts.com/modules/export-data.js"></script>
<script src="https://code.highcharts.com/modules/accessibility.js"></script> <script src="https://code.highcharts.com/modules/accessibility.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>