diff --git a/src/app/Button/button.component.html b/src/app/Button/button.component.html deleted file mode 100644 index 54fb491..0000000 --- a/src/app/Button/button.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/Button/button.component.ts b/src/app/Button/button.component.ts deleted file mode 100644 index c77daef..0000000 --- a/src/app/Button/button.component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from "@angular/core"; - - -@Component({ - selector: 'app-button', - standalone: true, - templateUrl: './button.component.html', -}) -export class ButtonComponent { - public text = 'Test'; -} diff --git a/src/app/Child/child.component.html b/src/app/Child/child.component.html deleted file mode 100644 index 84f430a..0000000 --- a/src/app/Child/child.component.html +++ /dev/null @@ -1,4 +0,0 @@ -

Child

-

Balance: {{balance}}

-

I'm broke. Now I'm about as poor as Jan-Marlon.

- diff --git a/src/app/Child/child.component.ts b/src/app/Child/child.component.ts deleted file mode 100644 index e03c3ad..0000000 --- a/src/app/Child/child.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, Input, Output, EventEmitter } from "@angular/core"; - -@Component({ - selector: 'app-child', - standalone: true, - templateUrl: './child.component.html', -}) -export class ChildComponent { - @Input() public balance: number = 0; - @Output() balanceChange = new EventEmitter(); - - public isBroke = false; - - public takeMoney() { - if (this.balance <= 0) { - this.isBroke = true; - } else { - this.balance -= 50; - if (this.balance == 0) { - this.isBroke = true; - } - this.balanceChange.emit(this.balance); - } - } -} diff --git a/src/app/Parent/parent.component.html b/src/app/Parent/parent.component.html deleted file mode 100644 index 6fc429c..0000000 --- a/src/app/Parent/parent.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
-

Parent

-

Kontostand: {{ balance }}

- - - - -
- diff --git a/src/app/Parent/parent.component.ts b/src/app/Parent/parent.component.ts deleted file mode 100644 index 502bb94..0000000 --- a/src/app/Parent/parent.component.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Component } from "@angular/core"; -import { ChildComponent } from "../Child/child.component"; - - -@Component({ - selector: 'app-parent', - standalone: true, - templateUrl: './parent.component.html', - imports: [ChildComponent], -}) -export class ParentComponent { - public balance = 1000; - - public addFifty() { - this.balance += 50; - } -} diff --git a/src/app/Parent/services/hotel.service.ts b/src/app/Parent/services/hotel.service.ts deleted file mode 100644 index 2eb1125..0000000 --- a/src/app/Parent/services/hotel.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Injectable } from "@angular/core"; -import { Hotel } from "../../HotelItem/hotel"; -import { from, Observable } from "rxjs"; - -@Injectable() -export class HotelService { - public getHotels(): Observable { - return from([ - { - "id": 1, - "hotelName": "Buea süßes Leben", - "description": "Schöne Aussicht am Meer", - "price": 230.5, - "imageUrl": "assets/img/1.jpg", - "rating": 3.5, - "tags": ["test"] - }, - { - "id": 2, - "hotelName": "Marrakesch", - "description": "Genießen Sie den Blick auf die Berge", - "price": 145.5, - "imageUrl": "assets/img/2.jpg", - "rating": 5, - "tags": ["test"] - }, - { - "id": 3, - "hotelName": "Abuja neuer Palast", - "description": "Kompletter Aufenthalt mit Autoservice", - "price": 120.12, - "imageUrl": "assets/img/3.jpg", - "rating": 4, - "tags": ["test"] - }, - { - "id": 4, - "hotelName": "Kapstadt Stadt", - "description": "Wunderschönes Ambiente für Ihren Aufenthalt", - "price": 135.12, - "imageUrl": "assets/img/4.jpg", - "rating": 2.5, - "tags": ["test"] - } - ]); - } -} diff --git a/src/app/Search/search.component.html b/src/app/Search/search.component.html index a408279..150b096 100644 --- a/src/app/Search/search.component.html +++ b/src/app/Search/search.component.html @@ -1 +1,2 @@ - + + diff --git a/src/app/Search/search.component.ts b/src/app/Search/search.component.ts index c3564a0..2c9bdb5 100644 --- a/src/app/Search/search.component.ts +++ b/src/app/Search/search.component.ts @@ -1,7 +1,5 @@ -import { CommonModule } from "@angular/common"; -import { Component, NgModule } from "@angular/core"; -import { FormsModule, NgForm, NgModel } from "@angular/forms"; -import { Input, Output } from "@angular/core"; +import { Component, Input, Output } from "@angular/core"; +import { FormsModule } from "@angular/forms"; import { EventEmitter } from "@angular/core"; @Component({ diff --git a/src/app/api/api.ts b/src/app/api/api.ts index b330010..6aac176 100644 --- a/src/app/api/api.ts +++ b/src/app/api/api.ts @@ -1,6 +1,6 @@ import { InMemoryDbService } from 'angular-in-memory-web-api'; -import { Hotel } from '../HotelItem/hotel'; +import { Hotel } from '../hotel-item/hotel'; /** * Initial data for in memory web api diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2a3bdb6..8725472 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,5 @@ import { Component, Injectable } from '@angular/core'; -import { HotelService } from './Parent/services/hotel.service'; +import { HotelService } from './service/hotel.service'; import { RouterOutlet } from '@angular/router'; @Injectable({providedIn: "root"}) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 46c3c18..f64b610 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,8 +2,8 @@ import { Routes } from '@angular/router'; import { HotelDetailsComponent } from './hotel-details/hotel-details.component'; import { HotelListComponent } from './hotel-list/hotel-list.component'; import { HotelFormComponent } from './hotel-form/hotel-form.component'; -import { inject } from '@angular/core'; import { formGuard } from './form.guard'; +import { NotFoundComponent } from './not-found/not-found.component'; export const routes: Routes = [ { @@ -20,4 +20,8 @@ export const routes: Routes = [ component: HotelFormComponent, canDeactivate: [formGuard], }, + { + path: "**", + component: NotFoundComponent, + }, ]; diff --git a/src/app/hotel-details/hotel-details.component.ts b/src/app/hotel-details/hotel-details.component.ts index 750c869..5721836 100644 --- a/src/app/hotel-details/hotel-details.component.ts +++ b/src/app/hotel-details/hotel-details.component.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Hotel } from '../HotelItem/hotel'; +import { Hotel } from '../hotel-item/hotel'; import { NgIf } from '@angular/common'; import { catchError, EMPTY } from 'rxjs'; import { HotelFormComponent } from "../hotel-form/hotel-form.component"; diff --git a/src/app/hotel-form/hotel-form.component.html b/src/app/hotel-form/hotel-form.component.html index 36b5ad9..64b99a8 100644 --- a/src/app/hotel-form/hotel-form.component.html +++ b/src/app/hotel-form/hotel-form.component.html @@ -1,57 +1,40 @@
-
- - -
-
Hotel Name is required.
-
Hotel Name must be at least 3 characters long.
-
-
-
- - -
-
Hotel Description is required.
-
Hotel Description must be at least 3 characters long. + +
+ + +
+ {{errorMessage}}
-
-
- - -
-
Hotel Price is required.
-
Hotel Price must be a positive number.
-
-
-
- - -
-
Hotel Rating is required.
-
Hotel Rating must be at least 0.
-
Hotel Rating cannot be more than 5.
-
-
+ +
+
+ {{errorMessage}} +
+
@@ -75,7 +62,7 @@

Are you sure you want to delete this hotel?

- +
diff --git a/src/app/hotel-form/hotel-form.component.ts b/src/app/hotel-form/hotel-form.component.ts index 0323c9a..5998b0e 100644 --- a/src/app/hotel-form/hotel-form.component.ts +++ b/src/app/hotel-form/hotel-form.component.ts @@ -1,10 +1,10 @@ -import { Component, HostListener, inject, Inject, Input } from '@angular/core'; -import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; -import { Hotel } from "../HotelItem/hotel"; +import { Component, inject, Input } from '@angular/core'; +import { AbstractControl, FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { Hotel } from "../hotel-item/hotel"; import { HttpClient } from '@angular/common/http'; -import { catchError } from "rxjs"; +import { catchError, debounceTime } from "rxjs"; import { Router, RouterLink } from "@angular/router"; -import { NgFor, NgIf } from "@angular/common"; +import { NgClass, NgFor, NgIf } from "@angular/common"; import { MatButtonModule } from "@angular/material/button"; import { MatIconModule } from "@angular/material/icon"; import { FormsModule } from '@angular/forms'; @@ -19,7 +19,8 @@ import { FormsModule } from '@angular/forms'; MatButtonModule, MatIconModule, NgFor, - FormsModule + FormsModule, + NgClass ], templateUrl: './hotel-form.component.html', styleUrl: './hotel-form.component.css' @@ -34,7 +35,9 @@ export class HotelFormComponent { public showDeleteConfirmation = false; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { } + + public errorMessage: string = ''; ngOnInit() { this.form = new FormGroup({ @@ -44,9 +47,18 @@ export class HotelFormComponent { rating: new FormControl(this.hotel?.rating || '', [Validators.min(0), Validators.max(5), Validators.required]), tags: new FormArray(this.hotel?.tags?.map(tag => new FormControl(tag)) || []) }); + + Object.keys(this.form.controls).forEach(key => { + const control = this.form.get(key); + control?.valueChanges.pipe( + debounceTime(1000) + ).subscribe(() => { + this.setError(control as AbstractControl, key); + }); + }); } - public delete(id: number) { + public delete() { this.http.delete(`api/hotels/${this.hotel?.id}`).pipe( catchError(err => { console.error('Error deleting hotel', err); @@ -68,7 +80,7 @@ export class HotelFormComponent { } public submit() { - if (this.form.valid && !this.form.dirty) { + if (this.form.valid && !this.errorMessage) { const hotelData: Hotel = { id: this.hotel?.id || 0, hotelName: this.form.value.name, @@ -101,7 +113,38 @@ export class HotelFormComponent { }); } + this.form.reset(); this.router.navigate(['/']); } } + + private validationErrors: Record = { + 'required': 'This field is required', + 'minlength': 'This field must be at least 3 characters long', + 'min': 'This field must be at least 0', + 'max': 'This field must be at most 5', + 'name.required': 'Hotel name is required', + 'name.minlength': 'Hotel name must be at least 3 characters long', + 'description.required': 'Hotel description is required', + 'description.minlength': 'Hotel description must be at least 3 characters long', + 'price.required': 'Hotel price is required', + 'price.min': 'Hotel price cannot be negative', + 'rating.required': 'Hotel rating is required', + 'rating.min': 'Hotel rating must be at least 0', + 'rating.max': 'Hotel rating cannot be more than 5', + 'tags.required': 'At least one tag is required', + }; + + private setError(control: AbstractControl, controlName: string) { + if ((control.touched || control.dirty) && control.errors) { + const errors = Object.keys(control.errors).map(key => { + const errorKey = `${controlName}.${key}`; + return this.validationErrors[errorKey] || this.validationErrors[key]; + }); + this.errorMessage = errors.join(' '); + } else { + this.errorMessage = ''; + } + } } + diff --git a/src/app/HotelItem/HotelItem.component.html b/src/app/hotel-item/HotelItem.component.html similarity index 100% rename from src/app/HotelItem/HotelItem.component.html rename to src/app/hotel-item/HotelItem.component.html diff --git a/src/app/HotelItem/HotelItem.component.ts b/src/app/hotel-item/HotelItem.component.ts similarity index 100% rename from src/app/HotelItem/HotelItem.component.ts rename to src/app/hotel-item/HotelItem.component.ts diff --git a/src/app/HotelItem/hotel.ts b/src/app/hotel-item/hotel.ts similarity index 100% rename from src/app/HotelItem/hotel.ts rename to src/app/hotel-item/hotel.ts diff --git a/src/app/hotel-list/hotel-list.component.html b/src/app/hotel-list/hotel-list.component.html index be67e98..5aab0f6 100644 --- a/src/app/hotel-list/hotel-list.component.html +++ b/src/app/hotel-list/hotel-list.component.html @@ -1,20 +1,20 @@
-

{{'hello' | uppercase | text}}

+

{{'hello' | uppercase | text}}

-
+
-
+
diff --git a/src/app/hotel-list/hotel-list.component.ts b/src/app/hotel-list/hotel-list.component.ts index 876c910..0711bfd 100644 --- a/src/app/hotel-list/hotel-list.component.ts +++ b/src/app/hotel-list/hotel-list.component.ts @@ -1,20 +1,14 @@ -import { CommonModule, NgFor, NgIf, UpperCasePipe } from '@angular/common'; +import { NgFor, NgIf, UpperCasePipe } from '@angular/common'; import { Component, inject } from '@angular/core'; import { TextPipe } from '../../text.pipe'; -import { SearchComponent } from '../Search/search.component'; -import { HotelService } from '../Parent/services/hotel.service'; -import { Hotel } from '../HotelItem/hotel'; +import { SearchComponent } from '../search/search.component'; +import { HotelService } from '../service/hotel.service'; +import { Hotel } from '../hotel-item/hotel'; import { HttpClient } from '@angular/common/http'; -import { filter, from, last, map, Observable, scan } from 'rxjs'; -import { HotelItem } from '../HotelItem/HotelItem.component'; +import { HotelItem } from '../hotel-item/HotelItem.component'; import { RouterLink } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; -interface User { - name: string; - age: number; -} - @Component({ selector: 'app-hotel-list', standalone: true, diff --git a/src/app/not-found/not-found.component.css b/src/app/not-found/not-found.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/not-found/not-found.component.html b/src/app/not-found/not-found.component.html new file mode 100644 index 0000000..62c460a --- /dev/null +++ b/src/app/not-found/not-found.component.html @@ -0,0 +1,7 @@ +
+
+

404

+

Page Not Found

+ Return Home +
+
diff --git a/src/app/not-found/not-found.component.spec.ts b/src/app/not-found/not-found.component.spec.ts new file mode 100644 index 0000000..5b65d9e --- /dev/null +++ b/src/app/not-found/not-found.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotFoundComponent } from './not-found.component'; + +describe('NotFoundComponent', () => { + let component: NotFoundComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NotFoundComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotFoundComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/not-found/not-found.component.ts b/src/app/not-found/not-found.component.ts new file mode 100644 index 0000000..10c6221 --- /dev/null +++ b/src/app/not-found/not-found.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'app-not-found', + standalone: true, + imports: [ + MatButtonModule, + RouterLink + ], + templateUrl: './not-found.component.html', + styleUrl: './not-found.component.css' +}) +export class NotFoundComponent { + +} diff --git a/src/app/service/hotel.service.ts b/src/app/service/hotel.service.ts new file mode 100644 index 0000000..31a5f02 --- /dev/null +++ b/src/app/service/hotel.service.ts @@ -0,0 +1,51 @@ +import { Injectable } from "@angular/core"; +import { Hotel } from "../hotel-item/hotel"; +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Observable, throwError, catchError } from "rxjs"; + +@Injectable({ + providedIn: 'root' +}) +export class HotelService { + private apiUrl = 'api/hotels'; + + constructor(private http: HttpClient) {} + + private handleError(error: HttpErrorResponse) { + let errorMessage = 'An error occurred'; + if (error.error instanceof ErrorEvent) { + // Client-side error + errorMessage = `Error: ${error.error.message}`; + } else { + // Server-side error + errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; + } + console.error(errorMessage); + return throwError(() => errorMessage); + } + + public getHotels(): Observable { + return this.http.get(this.apiUrl) + .pipe(catchError(this.handleError)); + } + + public getHotel(id: number): Observable { + return this.http.get(`${this.apiUrl}/${id}`) + .pipe(catchError(this.handleError)); + } + + public createHotel(hotel: Hotel): Observable { + return this.http.post(this.apiUrl, hotel) + .pipe(catchError(this.handleError)); + } + + public updateHotel(hotel: Hotel): Observable { + return this.http.put(`${this.apiUrl}/${hotel.id}`, hotel) + .pipe(catchError(this.handleError)); + } + + public deleteHotel(id: number): Observable<{}> { + return this.http.delete(`${this.apiUrl}/${id}`) + .pipe(catchError(this.handleError)); + } +} diff --git a/tailwind.config.js b/tailwind.config.js index 068e770..d5856ac 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,18 +1,9 @@ /** @type {import('tailwindcss').Config} */ -const colors = require('tailwindcss/colors'); - module.exports = { content: [ "./src/**/*.{html,ts}", ], - theme: { - extend: { - colors: { - ...colors, - } - }, - }, plugins: [], }