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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user