Add functionality to cancel tickets
Introduces a cancel order confirmation dialog and integrates it into the order flow. Refactors error and success components to support singular/plural seat messaging and retry actions. Updates navigation and button behaviors for better user experience. Fixes minor UI and logic issues in reservation, purchase, and conversion flows.
This commit is contained in:
@@ -69,6 +69,7 @@ import { CancellationSuccessComponent } from './cancellation-success/cancellatio
|
||||
import { CancellationFailedComponent } from './cancellation-failed/cancellation-failed.component';
|
||||
import { ConversionFailedComponent } from './conversion-failed/conversion-failed.component';
|
||||
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
||||
import { CancelOrderDialog } from './cancel-order/cancel-order.dialog';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -117,6 +118,7 @@ import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
||||
CancellationFailedComponent,
|
||||
ConversionFailedComponent,
|
||||
PayForOrderComponent,
|
||||
CancelOrderDialog,
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
|
||||
3
src/app/cancel-order/cancel-order.dialog.css
Normal file
3
src/app/cancel-order/cancel-order.dialog.css
Normal file
@@ -0,0 +1,3 @@
|
||||
button {
|
||||
min-width: 100px;
|
||||
}
|
||||
10
src/app/cancel-order/cancel-order.dialog.html
Normal file
10
src/app/cancel-order/cancel-order.dialog.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<h2 mat-dialog-title>Möchten Sie Ihre Bestellung wirklich stornieren?</h2>
|
||||
|
||||
<mat-dialog-content class="min-w-[400px]">
|
||||
<p class="text-sm text-gray-600 mb-2">Nach der Stornierung verlieren Sie Ihr Reservierungsrecht und die Sitzplätze können von anderen Kunden in Anspruch genommen werden. Dieser Prozess kann nicht rückgängig gemacht werden.</p>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions class="justify-end gap-2">
|
||||
<button mat-stroked-button color="warn" (click)="cancel()">Abbrechen</button>
|
||||
<button mat-flat-button color="primary" (click)="submit()">Stornieren</button>
|
||||
</mat-dialog-actions>
|
||||
22
src/app/cancel-order/cancel-order.dialog.ts
Normal file
22
src/app/cancel-order/cancel-order.dialog.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cancel-order',
|
||||
standalone: false,
|
||||
templateUrl: './cancel-order.dialog.html',
|
||||
styleUrl: './cancel-order.dialog.css',
|
||||
})
|
||||
export class CancelOrderDialog {
|
||||
constructor(
|
||||
private dialogRef: MatDialogRef<CancelOrderDialog>,
|
||||
) {}
|
||||
|
||||
submit(): void {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@
|
||||
warning
|
||||
</mat-icon>
|
||||
<h1 class="text-xl font-bold">Stornierung fehlgeschlagen!</h1>
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht storniert werden. Möglicherweise wurden die Tickets bereits bezahlt oder storniert.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/order" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Codeeingabe</button>
|
||||
<button mat-button type="button" matButton="filled" class="error-button mt-4 w-80" (click)="retry.emit()">Erneut versuchen</button>
|
||||
<button routerLink="/order" type="button" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Codeeingabe</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, input, output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cancellation-failed',
|
||||
@@ -7,5 +7,15 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './cancellation-failed.component.css',
|
||||
})
|
||||
export class CancellationFailedComponent {
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
retry = output<void>();
|
||||
|
||||
infoText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Leider konnten Ihre Sitzplätze nicht storniert werden. Möglicherweise wurden die Tickets bereits bezahlt oder storniert.' :
|
||||
'Leider konnte Ihr Sitzplatz nicht storniert werden. Möglicherweise wurde das Ticket bereits bezahlt oder storniert.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="bg-green-200 rounded-md shadow-sm w-full h-fit p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||
<div class="bg-indigo-100 rounded-md shadow-sm w-full h-fit p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||
<mat-icon class="material-symbols-outlined mb-5" style="font-size: 50px; width: 50px; height: 50px">
|
||||
task_alt
|
||||
</mat-icon>
|
||||
<h1 class="text-xl font-bold">Stornierung erfolgreich!</h1>
|
||||
<p class="text-center">Ihre Sitzplätze wurden erfolgreich storniert und stehen wieder zur Buchung zur Verfügnug.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<button routerLink="/schedule" mat-button matButton="filled" color="accent" class="success-button w-80 mt-4">Zur Programmauswahl</button>
|
||||
<button routerLink="/checkout/performance/{{performanceId()}}" mat-button matButton="outlined" class="success-button w-80 mt-1">Neue Tickets kaufen</button>
|
||||
<button routerLink="/schedule" type="button" mat-button matButton="filled" color="accent" class="w-80 mt-4">Zur Programmauswahl</button>
|
||||
<button type="button" mat-button matButton="outlined" class="w-80 mt-1" (click)="navigate()">Neue Tickets kaufen</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
import { Component, inject, input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cancellation-success',
|
||||
@@ -8,4 +9,19 @@ import { Component, input } from '@angular/core';
|
||||
})
|
||||
export class CancellationSuccessComponent {
|
||||
performanceId = input.required<number>();
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
router = inject(Router);
|
||||
|
||||
infoText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Ihre Sitzplätze wurden erfolgreich storniert und stehen wieder zur Buchung zur Verfügnug.' :
|
||||
'Ihr Sitzplatz wurden erfolgreich storniert und steht wieder zur Buchung zur Verfügnug.';
|
||||
}
|
||||
|
||||
navigate() {
|
||||
window.location.href = `/checkout/performance/${this.performanceId()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
warning
|
||||
</mat-icon>
|
||||
<h1 class="text-xl font-bold">Kauf fehlgeschlagen!</h1>
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht bezahlt werden. Möglicherweise wurden die Tickets bereits storniert.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/order" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Codeeingabe</button>
|
||||
<button mat-button type="button" matButton="filled" class="error-button mt-4 w-80" (click)="retry.emit()">Erneut versuchen</button>
|
||||
<button routerLink="/order" type="button" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Codeeingabe</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, input, output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-conversion-failed',
|
||||
@@ -7,5 +7,15 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './conversion-failed.component.css',
|
||||
})
|
||||
export class ConversionFailedComponent {
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
retry = output<void>();
|
||||
|
||||
infoText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Leider konnten Ihre Sitzplätze nicht bezahlt werden. Möglicherweise wurden die Tickets bereits storniert.' :
|
||||
'Leider konnte Ihr Sitzplatz nicht bezahlt werden. Möglicherweise wurde das Ticket bereits storniert.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { MatSnackBar, MatSnackBarRef } from '@angular/material/snack-bar';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -13,6 +13,9 @@ export class LoadingService {
|
||||
public loading$ = this.loadingSubject.asObservable();
|
||||
public error$ = this.errorSubject.asObservable();
|
||||
|
||||
private currentSnackBarRef?: MatSnackBarRef<any>;
|
||||
private currentSubscription?: Subscription;
|
||||
|
||||
constructor(private snackBar: MatSnackBar) {}
|
||||
|
||||
show(): void {
|
||||
@@ -23,6 +26,7 @@ export class LoadingService {
|
||||
hide(): void {
|
||||
this.loadingSubject.next(false);
|
||||
this.errorSubject.next(false);
|
||||
this.currentSnackBarRef?.dismiss();
|
||||
}
|
||||
|
||||
showError(messageOrError?: string | HttpErrorResponse | any): void {
|
||||
@@ -35,15 +39,22 @@ export class LoadingService {
|
||||
|
||||
const message = this.getErrorMessage(messageOrError);
|
||||
|
||||
const snackBarRef = this.snackBar.open(message, 'Schließen', {
|
||||
if (this.currentSnackBarRef) {
|
||||
this.currentSubscription?.unsubscribe();
|
||||
this.currentSnackBarRef.dismiss();
|
||||
}
|
||||
|
||||
this.currentSnackBarRef = this.snackBar.open(message, 'Schließen', {
|
||||
duration: 0,
|
||||
panelClass: ['error-snackbar'],
|
||||
horizontalPosition: 'center',
|
||||
verticalPosition: 'bottom'
|
||||
});
|
||||
|
||||
snackBarRef.afterDismissed().subscribe(() => {
|
||||
this.currentSubscription = this.currentSnackBarRef.afterDismissed().subscribe(() => {
|
||||
if (!this.loadingSubject.value) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="flex items-center space-x-4">
|
||||
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||
<mat-label>Film online suchen</mat-label>
|
||||
<input class="w-full" type="text" matInput [formControl]="formControl">
|
||||
<input class="w-full" type="text" matInput [formControl]="formControl"/>
|
||||
@if (formControl.hasError('noResults')) {
|
||||
<mat-error>Keine Suchergebnisse gefunden</mat-error>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { LoadingService } from './../loading.service';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, DestroyRef, inject } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { catchError, finalize, of, tap } from 'rxjs';
|
||||
import { HttpService } from '../http.service';
|
||||
import { OmdbMovie } from '@infinimotion/model-frontend';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-movie-importer',
|
||||
@@ -21,13 +22,15 @@ export class MovieImporterComponent {
|
||||
|
||||
private httpService = inject(HttpService)
|
||||
public loadingService = inject(LoadingService)
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
|
||||
DoSubmit() {
|
||||
this.showAll = false;
|
||||
this.searchForMovies();
|
||||
}
|
||||
|
||||
searchForMovies() {
|
||||
private searchForMovies() {
|
||||
this.search_query = this.formControl.value?.trim() || '';
|
||||
if (this.search_query?.length == 0) return;
|
||||
|
||||
@@ -48,7 +51,8 @@ export class MovieImporterComponent {
|
||||
finalize(() => {
|
||||
this.isSearching = false;
|
||||
this.formControl.enable();
|
||||
})
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
:host {
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
mat-stepper {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
|
||||
<mat-stepper orientation="horizontal" linear="true" [selectedIndex]="isResuming()? 2 : 0" [disableRipple]="true" (selectionChange)="onStepChange($event)" #stepper>
|
||||
<mat-step [editable]="!isResuming()">
|
||||
<mat-step [editable]="!isResuming() && !(isReservationSuccess() && !isSubmitting()) && !(isPurchaseSuccess() && !isSubmitting())">
|
||||
<ng-template matStepLabel>Warenkorb</ng-template>
|
||||
|
||||
<div class="performance-info-space"></div>
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [editable]="!isResuming" [stepControl]="dataForm">
|
||||
<mat-step [editable]="!isResuming() && !(isPurchaseSuccess() && !isSubmitting())" [completed]="isResuming() || dataForm.valid || isPurchaseSuccess() || isSubmitting()" [stepControl]="dataForm">
|
||||
<form [formGroup]="dataForm">
|
||||
<ng-template matStepLabel>Anschrift</ng-template>
|
||||
|
||||
@@ -75,11 +75,11 @@
|
||||
|
||||
@if (isReservationSuccess() && !isSubmitting()) {
|
||||
<div class="h-4"></div>
|
||||
<app-reservation-success [order]="createdOrder()!"></app-reservation-success>
|
||||
<app-reservation-success [order]="createdOrder()!" [moreThanOne]="totalSeats() > 1"></app-reservation-success>
|
||||
}
|
||||
@else if (isReservationError() && !isSubmitting()) {
|
||||
@else if (isReservationError() && !isSubmitting() && performance()) {
|
||||
<div class="h-4"></div>
|
||||
<app-reservation-failed></app-reservation-failed>
|
||||
<app-reservation-failed (retry)="retryReservation()" [performanceId]="performance()!.id" [moreThanOne]="totalSeats() > 1"></app-reservation-failed>
|
||||
}
|
||||
@else {
|
||||
|
||||
@@ -123,23 +123,23 @@
|
||||
|
||||
@if (isPurchaseSuccess() && !isSubmitting()) {
|
||||
<div class="h-4"></div>
|
||||
<app-purchase-success [tickets]="createdTickets()"></app-purchase-success>
|
||||
<app-purchase-success [tickets]="createdTickets()" [moreThanOne]="totalSeats() > 1"></app-purchase-success>
|
||||
}
|
||||
@else if (isPurchaseError() && !isSubmitting()) {
|
||||
@else if (isPurchaseError() && !isSubmitting() && performance()) {
|
||||
<div class="h-4"></div>
|
||||
<app-purchase-failed></app-purchase-failed>
|
||||
<app-purchase-failed (retry)="retryPurchase()" [performanceId]="performance()!.id" [moreThanOne]="totalSeats() > 1"></app-purchase-failed>
|
||||
}
|
||||
@else if (isConversionError() && !isSubmitting()) {
|
||||
<div class="h-4"></div>
|
||||
<app-conversion-failed></app-conversion-failed>
|
||||
<app-conversion-failed (retry)="retryConversion()" [moreThanOne]="totalSeats() > 1"></app-conversion-failed >
|
||||
}
|
||||
@else if (isCancellationSuccess() && !isSubmitting() && performance()) {
|
||||
<div class="h-4"></div>
|
||||
<app-cancellation-success [performanceId]="performance()!.id"></app-cancellation-success>
|
||||
<app-cancellation-success [performanceId]="performance()!.id" [moreThanOne]="totalSeats() > 1"></app-cancellation-success>
|
||||
}
|
||||
@else if (isCancellationError() && !isSubmitting()) {
|
||||
<div class="h-4"></div>
|
||||
<app-cancellation-failed></app-cancellation-failed>
|
||||
<app-cancellation-failed (retry)="retryCancellation()" [moreThanOne]="totalSeats() > 1"></app-cancellation-failed>
|
||||
}
|
||||
@else {
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { HttpService } from '../http.service';
|
||||
import { catchError, tap, finalize, EMPTY } from 'rxjs';
|
||||
import { MatStepper } from '@angular/material/stepper';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CancelOrderDialog } from '../cancel-order/cancel-order.dialog';
|
||||
|
||||
type OrderState =
|
||||
| { status: 'idle' }
|
||||
@@ -32,6 +34,7 @@ export class OrderComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
private httpService = inject(HttpService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
readonly loadingService = inject(LoadingService);
|
||||
readonly selectedSeatsService = inject(SelectedSeatsService);
|
||||
@@ -41,6 +44,7 @@ export class OrderComponent {
|
||||
|
||||
existingOrder = input<Bestellung>();
|
||||
existingTickets = input<Eintrittskarte[]>();
|
||||
resumeWithCancel = input<boolean>(true);
|
||||
|
||||
stepChanged = output<number>();
|
||||
|
||||
@@ -130,6 +134,10 @@ export class OrderComponent {
|
||||
cvv: ['', [Validators.required, Validators.pattern(/^\d{3,4}$/)]],
|
||||
});
|
||||
this.confetti = (await import('canvas-confetti')).default;
|
||||
|
||||
if (this.resumeWithCancel()) {
|
||||
this.cancelReservation();
|
||||
}
|
||||
}
|
||||
|
||||
get fData() { return this.dataForm.controls; }
|
||||
@@ -161,6 +169,7 @@ export class OrderComponent {
|
||||
this.makeReservation();
|
||||
} else if (this.submissionMode() === 'purchase') {
|
||||
stepper.next();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,7 +237,7 @@ export class OrderComponent {
|
||||
}
|
||||
|
||||
private submitOrder(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung, mode: SubmissionMode) {
|
||||
this.loadingService.hide();
|
||||
this.loadingService.show();
|
||||
|
||||
// Tickets anlegen
|
||||
const tickets = seats.map(seat => {
|
||||
@@ -324,7 +333,23 @@ export class OrderComponent {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
cancelReservation() {
|
||||
const dialogRef = this.dialog.open(CancelOrderDialog, {
|
||||
width: '500px',
|
||||
disableClose: false,
|
||||
enterAnimationDuration: '200ms',
|
||||
exitAnimationDuration: '100ms'
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(confirmed => {
|
||||
if (confirmed) {
|
||||
this.performCancellation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private performCancellation() {
|
||||
const order = this.existingOrder()!;
|
||||
order.cancelled = new Date();
|
||||
|
||||
@@ -365,4 +390,26 @@ export class OrderComponent {
|
||||
getPriceDisplay(price: number): string {
|
||||
return `${(price / 100).toFixed(2)} €`;
|
||||
}
|
||||
|
||||
retryPurchase() {
|
||||
this.orderState.set({ status: 'idle' });
|
||||
this.makePurchase();
|
||||
}
|
||||
|
||||
retryReservation() {
|
||||
this.orderState.set({ status: 'idle' });
|
||||
this.makeReservation();
|
||||
}
|
||||
|
||||
retryConversion() {
|
||||
this.orderState.set({ status: 'idle' });
|
||||
const order = this.existingOrder()!;
|
||||
order.booked = new Date();
|
||||
this.convertOrder(order, this.existingTickets()!);
|
||||
}
|
||||
|
||||
retryCancellation() {
|
||||
this.orderState.set({ status: 'idle' });
|
||||
this.cancelReservation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<input class="w-full" type="text" matInput [formControl]="formControl" (input)="onInput($event)" placeholder="XXXXXX" maxlength="6" autocomplete="off">
|
||||
<mat-error>
|
||||
@if (formControl.hasError('invalid')) {
|
||||
Ungültiger Bestellcode
|
||||
Ungültiger Reservierungscode
|
||||
}
|
||||
@else if (formControl.hasError('completed')) {
|
||||
Diese Bestellung wurde bereits abgeschlossen
|
||||
@@ -23,10 +23,10 @@
|
||||
Diese Bestellung wurde bereits bezahlt
|
||||
}
|
||||
@else if (formControl.hasError('cancelled')) {
|
||||
Diese Bestellung wurde storniert
|
||||
Diese Reservierung wurde storniert
|
||||
}
|
||||
@else if (formControl.hasError('serverError')) {
|
||||
Fehler beim Laden der Bestellung
|
||||
Fehler beim Laden der Reservierung
|
||||
}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FormControl, Validators } from '@angular/forms';
|
||||
import { LoadingService } from '../loading.service';
|
||||
import { HttpService } from '../http.service';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { catchError, map, of, take } from 'rxjs';
|
||||
import { catchError, finalize, map, of, take } from 'rxjs';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
warning
|
||||
</mat-icon>
|
||||
<h1 class="text-xl font-bold">Kauf fehlgeschlagen!</h1>
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<button mat-button type="button" matButton="filled" class="error-button mt-4 w-80" (click)="retry.emit()">Erneut versuchen</button>
|
||||
<button mat-button type="button" matButton="outlined" color="accent" class="error-button w-80 mt-1" (click)="navigate()">Zurück zur Sitzplatzauswahl</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, inject, input, output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-purchase-failed',
|
||||
@@ -7,5 +8,22 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './purchase-failed.component.css',
|
||||
})
|
||||
export class PurchaseFailedComponent {
|
||||
performanceId = input.required<number>();
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
retry = output<void>();
|
||||
|
||||
private router = inject(Router);
|
||||
|
||||
infoText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.' :
|
||||
'Leider konnte Ihr Sitzplatz nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, denselben Sitzplatz zu kaufen.';
|
||||
}
|
||||
|
||||
navigate() {
|
||||
window.location.href = `/checkout/performance/${this.performanceId()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="bg-green-200 rounded-md shadow-sm w-full h-fit p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||
<h1 class="text-xl font-bold">Vielen Dank für Ihren Einkauf!</h1>
|
||||
<p class="text-center">Ihre Sitzplätze wurden erfolgreich gebucht.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<app-ticket-list [tickets]="tickets()" class="w-8/10 my-4"></app-ticket-list>
|
||||
|
||||
<button mat-button disabled="true" matButton="filled" class="success-button w-80 mt-4">Tickets herunterladen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="success-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<button mat-button disabled="true" matButton="filled" class="success-button w-80 mt-4">{{ buttonText }}</button>
|
||||
<button routerLink="/schedule" type="button" mat-button matButton="outlined" color="accent" class="success-button w-80 mt-1">Zur Programmauswahl</button>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,4 +9,18 @@ import { Component, input } from '@angular/core';
|
||||
})
|
||||
export class PurchaseSuccessComponent {
|
||||
tickets = input.required<Eintrittskarte[]>();
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
infoText!: string;
|
||||
buttonText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Ihre Sitzplätze wurden erfolgreich gebucht.' :
|
||||
'Ihr Sitzplatz wurden erfolgreich gebucht.';
|
||||
|
||||
this.buttonText = this.moreThanOne()?
|
||||
'Tickets herunterladen' :
|
||||
'Ticket herunterladen';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
warning
|
||||
</mat-icon>
|
||||
<h1 class="text-xl font-bold">Reservierung fehlgeschlagen!</h1>
|
||||
<p class="text-center">Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.</p>
|
||||
<p class="text-center">{{ infoText }}</p>
|
||||
|
||||
<button mat-button matButton="filled" class="error-button mt-4 w-80">Erneut versuchen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" color="accent" class="error-button w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<button mat-button type="button" matButton="filled" class="error-button mt-4 w-80" (click)="retry.emit()">Erneut versuchen</button>
|
||||
<button mat-button type="button" matButton="outlined" color="accent" class="error-button w-80 mt-1" (click)="navigate()">Zurück zur Sitzplatzauswahl</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Component, inject, input, output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reservation-failed',
|
||||
@@ -7,5 +8,22 @@ import { Component } from '@angular/core';
|
||||
styleUrl: './reservation-failed.component.css',
|
||||
})
|
||||
export class ReservationFailedComponent {
|
||||
performanceId = input.required<number>();
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
retry = output<void>();
|
||||
|
||||
router = inject(Router)
|
||||
|
||||
infoText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.' :
|
||||
'Leider konnte Ihr Sitzplatz nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, denselben Sitzplatz zu reservieren.';
|
||||
}
|
||||
|
||||
navigate() {
|
||||
window.location.href = `/checkout/performance/${this.performanceId()}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<div class="bg-green-200 rounded-md shadow-sm w-full h-fit p-6 py-8 items-center justify-center flex flex-col space-y-2">
|
||||
<h1 class="text-xl font-bold">Reservierung erfolgreich!</h1>
|
||||
|
||||
<p class="text-center">Ihre Sitzplätze wurden erfolgreich reserviert. Bitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln.</p>
|
||||
<p class="text-center" style="white-space: pre-line;">{{ infoText }}</p>
|
||||
<div class="bg-white text-5xl font-mono rounded-md shadow-sm w-fit h-fit p-4 py-2 my-4">
|
||||
<strong>{{ order().code }}</strong>
|
||||
</div>
|
||||
|
||||
<button routerLink="/checkout/order/{{ order().code }}" mat-button matButton="filled" color="accent" class="success-button mt-2 w-80">Tickets jetzt online bezahlen</button>
|
||||
<button routerLink="/schedule" mat-button matButton="outlined" class="success-button mb-4 w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<div class="text-green-500 cursor-pointer w-fit mt-2" (click)="cancelReservation()">
|
||||
<button routerLink="/checkout/order/{{ order().code }}" type="button" mat-button matButton="filled" color="accent" class="success-button mt-2 w-80">{{ buttonText }}</button>
|
||||
<button routerLink="/schedule" type="button" mat-button matButton="outlined" class="success-button mb-4 w-80 mt-1">Zurück zur Programmauswahl</button>
|
||||
<div [routerLink]="['/checkout/order', order().code]" [queryParams]="{ action: 'cancel' }" class="text-green-500 cursor-pointer w-fit mt-2">
|
||||
Reservierung stornieren
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bestellung } from '@infinimotion/model-frontend';
|
||||
import { Component, input } from '@angular/core';
|
||||
import { Component, input, OnInit, output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reservation-success',
|
||||
@@ -7,10 +7,20 @@ import { Component, input } from '@angular/core';
|
||||
templateUrl: './reservation-success.component.html',
|
||||
styleUrl: './reservation-success.component.css',
|
||||
})
|
||||
export class ReservationSuccessComponent {
|
||||
export class ReservationSuccessComponent implements OnInit {
|
||||
order = input.required<Bestellung>();
|
||||
moreThanOne = input<boolean>(false);
|
||||
|
||||
cancelReservation() {
|
||||
// Logic to cancel the reservation
|
||||
infoText!: string;
|
||||
buttonText!: string;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.infoText = this.moreThanOne()?
|
||||
'Ihre Sitzplätze wurden erfolgreich reserviert.\nBitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln' :
|
||||
'Ihr Sitzplatz wurde erfolgreich reserviert.\nBitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln';
|
||||
|
||||
this.buttonText = this.moreThanOne()?
|
||||
'Tickets jetzt online bezahlen' :
|
||||
'Ticket jetzt online bezahlen';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export class SelectedSeatsService {
|
||||
}
|
||||
|
||||
commit(): void {
|
||||
this.erroredSignal.set(false);
|
||||
this.committedSignal.set(true);
|
||||
}
|
||||
|
||||
@@ -69,6 +70,7 @@ export class SelectedSeatsService {
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.erroredSignal.set(false);
|
||||
this.cancelledSignal.set(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-order (stepChanged)="setStepTwoOrHigher($event >= 1)" class="mt-10 mr-30 w-3/10" [performance]="performance()" [seatCategories]="seatCategories" [existingOrder]="isResuming? order : undefined" [existingTickets]="isResuming? tickets : undefined"></app-order>
|
||||
<app-order (stepChanged)="setStepTwoOrHigher($event >= 1)" class="mt-10 mr-30 w-3/10" [performance]="performance()" [seatCategories]="seatCategories" [existingOrder]="isResuming? order : undefined" [existingTickets]="isResuming? tickets : undefined" [resumeWithCancel] = "resumeWithCancel"></app-order>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -31,6 +31,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
orderId?: number;
|
||||
orderCode?: string | null;
|
||||
isResuming = false;
|
||||
resumeWithCancel = false;
|
||||
tickets: Eintrittskarte[] | undefined;
|
||||
order: Bestellung | undefined;
|
||||
blockedSeats: Sitzplatz[] | undefined;
|
||||
@@ -56,8 +57,14 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
if (this.orderCode) {
|
||||
// Checkout fortsetzen
|
||||
this.isResuming = true;
|
||||
|
||||
this.loadExistingOrder(this.orderCode);
|
||||
|
||||
this.route.queryParams.subscribe(params => {
|
||||
if (params['action'] === 'cancel') {
|
||||
this.resumeWithCancel = true;
|
||||
}
|
||||
});
|
||||
} else if (this.showId) {
|
||||
// Neuer Checkout
|
||||
this.isResuming = false;
|
||||
|
||||
Reference in New Issue
Block a user