diff --git a/src/app/app-module.ts b/src/app/app-module.ts index a8554ec..c625000 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -65,6 +65,9 @@ import { TicketSmallComponent } from './ticket-small/ticket-small.component'; import { TicketListComponent } from './ticket-list/ticket-list.component'; import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; import { SelectionConflictInfoComponent } from './selection-conflict-info/selection-conflict-info.component'; +import { CancellationSuccessComponent } from './cancellation-success/cancellation-success.component'; +import { CancellationFailedComponent } from './cancellation-failed/cancellation-failed.component'; +import { ConversionFailedComponent } from './conversion-failed/conversion-failed.component'; @NgModule({ @@ -109,6 +112,9 @@ import { SelectionConflictInfoComponent } from './selection-conflict-info/select TicketListComponent, ZoomWarningComponent, SelectionConflictInfoComponent, + CancellationSuccessComponent, + CancellationFailedComponent, + ConversionFailedComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index e5692a4..1678c5f 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -27,7 +27,8 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee' }, - { path: 'performance/:performanceId/checkout', component: TheaterOverlayComponent}, + { path: 'checkout/performance/:performanceId', component: TheaterOverlayComponent}, + { path: 'checkout/order/:orderId', component: TheaterOverlayComponent}, ], }, diff --git a/src/app/cancellation-failed/cancellation-failed.component.css b/src/app/cancellation-failed/cancellation-failed.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/cancellation-failed/cancellation-failed.component.html b/src/app/cancellation-failed/cancellation-failed.component.html new file mode 100644 index 0000000..0bd5a1d --- /dev/null +++ b/src/app/cancellation-failed/cancellation-failed.component.html @@ -0,0 +1,11 @@ +
+ + warning + +

Stornierung fehlgeschlagen!

+

Leider konnten Ihre Sitzplätze nicht storniert werden. Möglicherweise wurden die Tickets bereits bezahlt oder storniert.

+ + + +
+ diff --git a/src/app/cancellation-failed/cancellation-failed.component.ts b/src/app/cancellation-failed/cancellation-failed.component.ts new file mode 100644 index 0000000..a6e7519 --- /dev/null +++ b/src/app/cancellation-failed/cancellation-failed.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-cancellation-failed', + standalone: false, + templateUrl: './cancellation-failed.component.html', + styleUrl: './cancellation-failed.component.css', +}) +export class CancellationFailedComponent { + +} diff --git a/src/app/cancellation-success/cancellation-success.component.css b/src/app/cancellation-success/cancellation-success.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/cancellation-success/cancellation-success.component.html b/src/app/cancellation-success/cancellation-success.component.html new file mode 100644 index 0000000..d7d1c22 --- /dev/null +++ b/src/app/cancellation-success/cancellation-success.component.html @@ -0,0 +1,11 @@ +
+ + task_alt + +

Stornierung erfolgreich!

+

Ihre Sitzplätze wurden erfolgreich storniert und stehen wieder zur Buchung zur Verfügnug.

+ + + + +
diff --git a/src/app/cancellation-success/cancellation-success.component.ts b/src/app/cancellation-success/cancellation-success.component.ts new file mode 100644 index 0000000..75e645f --- /dev/null +++ b/src/app/cancellation-success/cancellation-success.component.ts @@ -0,0 +1,11 @@ +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-cancellation-success', + standalone: false, + templateUrl: './cancellation-success.component.html', + styleUrl: './cancellation-success.component.css', +}) +export class CancellationSuccessComponent { + performanceId = input.required(); +} diff --git a/src/app/conversion-failed/conversion-failed.component.css b/src/app/conversion-failed/conversion-failed.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/conversion-failed/conversion-failed.component.html b/src/app/conversion-failed/conversion-failed.component.html new file mode 100644 index 0000000..d822ae3 --- /dev/null +++ b/src/app/conversion-failed/conversion-failed.component.html @@ -0,0 +1,11 @@ +
+ + warning + +

Kauf fehlgeschlagen!

+

Leider konnten Ihre Sitzplätze nicht bezahlt werden. Möglicherweise wurden die Tickets bereits storniert.

+ + + +
+ diff --git a/src/app/conversion-failed/conversion-failed.component.ts b/src/app/conversion-failed/conversion-failed.component.ts new file mode 100644 index 0000000..9ba4c68 --- /dev/null +++ b/src/app/conversion-failed/conversion-failed.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-conversion-failed', + standalone: false, + templateUrl: './conversion-failed.component.html', + styleUrl: './conversion-failed.component.css', +}) +export class ConversionFailedComponent { + +} diff --git a/src/app/http.service.ts b/src/app/http.service.ts index ede6c37..3db7819 100644 --- a/src/app/http.service.ts +++ b/src/app/http.service.ts @@ -33,9 +33,9 @@ export class HttpService { return this.http.post(`${this.baseUrl}bestellung`, order); } - /* PUT /api/bestellung/{id} */ - updateOrder(id: number, order: Partial): Observable { - return this.http.put(`${this.baseUrl}bestellung/${id}`, order); + /* PUT /api/bestellung */ + updateOrder(order: Partial): Observable { + return this.http.put(`${this.baseUrl}bestellung`, order); } /* DELETE /api/bestellung/{id} */ diff --git a/src/app/movie-performance/movie-performance.component.ts b/src/app/movie-performance/movie-performance.component.ts index 6f98ff9..217cec6 100644 --- a/src/app/movie-performance/movie-performance.component.ts +++ b/src/app/movie-performance/movie-performance.component.ts @@ -15,7 +15,7 @@ export class MoviePerformanceComponent implements OnInit { route: string = ''; ngOnInit() { - this.route = `../performance/${this.id()}/checkout`; + this.route = `../checkout/performance/${this.id()}`; } startTime = computed(() => diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html index 5e8dffd..f9a2b58 100644 --- a/src/app/order/order.component.html +++ b/src/app/order/order.component.html @@ -27,47 +27,47 @@ } - - - Warenkorb + + + Warenkorb -
+
- @if (selectedSeatsService.hadConflict()) { - + @if (selectedSeatsService.hadConflict()) { + + } + + +
+ @for (seatCategory of seatCategories(); track $index) { +
+ } + @empty { + + } +
- -
- @for (seatCategory of seatCategories(); track $index) { -
- - } - @empty { - - } -
+ - + +
+

+ Tickets gesamt: +

+

+ {{ getPriceDisplay(totalPrice()) }} +

+
- -
-

- Tickets gesamt: -

-

- {{ getPriceDisplay(totalPrice()) }} -

-
- - -
- - -
+ +
+ + +
- +
Anschrift @@ -129,6 +129,18 @@
} + @else if (isConversionError() && !isSubmitting()) { +
+ + } + @else if (isCancellationSuccess() && !isSubmitting() && performance()) { +
+ + } + @else if (isCancellationError() && !isSubmitting()) { +
+ + } @else { @@ -183,9 +195,17 @@
- + + @if (isResuming()) { + + } @else { + + } + diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts index 4d2665d..e3aa851 100644 --- a/src/app/order/order.component.ts +++ b/src/app/order/order.component.ts @@ -15,7 +15,10 @@ type OrderState = | { status: 'reservation-success'; order: Bestellung } | { status: 'reservation-error'; error: any } | { status: 'purchase-success'; tickets: Eintrittskarte[] } - | { status: 'purchase-error'; error: any }; + | { status: 'purchase-error'; error: any } + | { status: 'conversion-error'; error: any } + | { status: 'cancellation-success'; } + | { status: 'cancellation-error'; error: any }; type SubmissionMode = 'reservation' | 'purchase'; @@ -36,6 +39,9 @@ export class OrderComponent { performance = input(); seatCategories = input.required(); + existingOrder = input(); + existingTickets = input(); + stepChanged = output(); paymentForm!: FormGroup; @@ -52,6 +58,16 @@ export class OrderComponent { isSubmitting = computed(() => this.orderState().status === 'submitting'); + isResuming = computed(() => { + const order = this.existingOrder(); + const tickets = this.existingTickets(); + + if (!order || !tickets || tickets.length === 0) { + return false; + } + return true; + }); + secondPhaseButtonText = computed(() => { const mode = this.submissionMode(); if (!mode) return 'Loading...'; @@ -70,6 +86,10 @@ export class OrderComponent { this.orderState().status === 'purchase-success' ); + isCancellationSuccess = computed(() => + this.orderState().status === 'cancellation-success' + ); + isReservationError = computed(() => this.orderState().status === 'reservation-error' ); @@ -78,6 +98,14 @@ export class OrderComponent { this.orderState().status === 'purchase-error' ); + isConversionError = computed(() => + this.orderState().status === 'conversion-error' + ); + + isCancellationError = computed(() => + this.orderState().status === 'cancellation-error' + ); + createdOrder = computed(() => { const state = this.orderState(); return state.status === 'reservation-success' ? state.order : null; @@ -138,7 +166,6 @@ export class OrderComponent { makeReservation() { this.orderState.set({ status: 'submitting' }); - this.loadingService.show(); this.disableForms(); const order = this.generateNewOrderObject(this.dataForm.value.email, false); @@ -157,15 +184,51 @@ export class OrderComponent { this.loadingService.show(); this.disableForms(); - const order = this.generateNewOrderObject(this.dataForm.value.email, true); - const seats = this.selectedSeatsService.selectedSeats(); - const performance = this.performance()!; + if (this.isResuming()) { + const order = this.existingOrder()!; + order.booked = new Date(); + this.convertOrder(order, this.existingTickets()!); + } else { + const order = this.generateNewOrderObject(this.dataForm.value.email, true); + const seats = this.selectedSeatsService.selectedSeats(); + const performance = this.performance()!; - this.submitOrder(order, seats, performance, 'purchase'); + this.submitOrder(order, seats, performance, 'purchase'); + } } + private convertOrder(order: Bestellung, tickets: Eintrittskarte[]) { + this.loadingService.show(); + this.httpService.updateOrder(order).pipe( + tap(() => { + // Success Handling + this.orderState.set({ + status: 'purchase-success', + tickets: tickets + }); - submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) { + this.selectedSeatsService.commit(); + this.loadingService.hide(); + this.showConfetti(); + }), + catchError(err => { + // Error handling + this.selectedSeatsService.error(); + this.loadingService.showError(err); + console.error('Fehler bei der Umwandlung der Bestellung:', err); + this.orderState.set({status: 'conversion-error', error: err}); + + return EMPTY; + }), + finalize(() => { + this.enableForms(); + }), + takeUntilDestroyed(this.destroyRef) + ).subscribe(); + } + + private submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) { + this.loadingService.hide(); // Tickets anlegen const tickets = seats.map(seat => { @@ -194,8 +257,9 @@ export class OrderComponent { }), catchError(err => { // Error handling - console.error('Fehler beim Anlegen der Bestellung/Tickets:', err); + this.selectedSeatsService.error(); this.loadingService.showError(err); + console.error('Fehler beim Anlegen der Bestellung/Tickets:', err); if (mode === 'reservation') { this.orderState.set({ status: 'reservation-error', error: err }); @@ -260,6 +324,34 @@ export class OrderComponent { }; } + cancelReservation() { + const order = this.existingOrder()!; + order.cancelled = new Date(); + + this.loadingService.show(); + this.httpService.updateOrder(order).pipe( + tap(() => { + // Success Handling + this.orderState.set({ + status: 'cancellation-success' + }); + + this.selectedSeatsService.cancel(); + this.loadingService.hide(); + }), + catchError(err => { + // Error handling + this.selectedSeatsService.error(); + this.loadingService.showError(err); + console.error('Fehler bei der Bezahlung der Bestellung:', err); + this.orderState.set({status: 'cancellation-error', error: err}); + + return EMPTY; + }), + takeUntilDestroyed(this.destroyRef) + ).subscribe(); + } + private disableForms(): void { this.dataForm.disable(); this.paymentForm.disable(); diff --git a/src/app/reservation-success/reservation-success.component.html b/src/app/reservation-success/reservation-success.component.html index 8d28e59..a8c961c 100644 --- a/src/app/reservation-success/reservation-success.component.html +++ b/src/app/reservation-success/reservation-success.component.html @@ -6,7 +6,7 @@ {{ order().code }}
- +
Reservierung stornieren diff --git a/src/app/seat/seat.component.html b/src/app/seat/seat.component.html index 975c099..f4a1015 100644 --- a/src/app/seat/seat.component.html +++ b/src/app/seat/seat.component.html @@ -1,4 +1,4 @@ -