Add pay-for-order component for ticket payment
Introduces PayForOrderComponent with form validation and error handling for ticket payment by order code. Updates routing, navigation, and module declarations to support the new feature.
This commit is contained in:
@@ -68,6 +68,7 @@ import { SelectionConflictInfoComponent } from './selection-conflict-info/select
|
||||
import { CancellationSuccessComponent } from './cancellation-success/cancellation-success.component';
|
||||
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';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -115,6 +116,7 @@ import { ConversionFailedComponent } from './conversion-failed/conversion-failed
|
||||
CancellationSuccessComponent,
|
||||
CancellationFailedComponent,
|
||||
ConversionFailedComponent,
|
||||
PayForOrderComponent,
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ScheduleComponent } from './schedule/schedule.component';
|
||||
import { TheaterOverlayComponent} from './theater-overlay/theater-overlay.component';
|
||||
import { MovieImporterComponent } from './movie-importer/movie-importer.component';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { PayForOrderComponent } from './pay-for-order/pay-for-order.component';
|
||||
|
||||
const routes: Routes = [
|
||||
// Seiten ohne Layout
|
||||
@@ -29,6 +30,7 @@ const routes: Routes = [
|
||||
},
|
||||
{ path: 'checkout/performance/:performanceId', component: TheaterOverlayComponent},
|
||||
{ path: 'checkout/order/:orderId', component: TheaterOverlayComponent},
|
||||
{ path: 'checkout/order', component: PayForOrderComponent},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Component, inject, computed, OnInit } from '@angular/core';
|
||||
export class NavbarComponent {
|
||||
navItems: { label:string, path:string }[] = [
|
||||
{label: 'Programm', path: '/schedule'},
|
||||
{label: 'Bezahlen', path: '/checkout/order'},
|
||||
{label: 'Film importieren', path: '/admin/movie-importer'},
|
||||
]
|
||||
|
||||
|
||||
7
src/app/pay-for-order/pay-for-order.component.css
Normal file
7
src/app/pay-for-order/pay-for-order.component.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.middle {
|
||||
position: relative;
|
||||
top: 40%;
|
||||
-webkit-transform: translateY(-50%);
|
||||
-ms-transform: translateY(-50%);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
40
src/app/pay-for-order/pay-for-order.component.html
Normal file
40
src/app/pay-for-order/pay-for-order.component.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<app-menu-header label="Tickets bezahlen" icon="payment_card"></app-menu-header>
|
||||
|
||||
<div class="w-100 m-auto middle">
|
||||
<form class="order-search-form w-full" (ngSubmit)="DoSubmit()">
|
||||
<div class="flex items-center space-x-4">
|
||||
<mat-form-field class="w-full" subscriptSizing="dynamic">
|
||||
<mat-label>Reservierungsnummer eingeben</mat-label>
|
||||
<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
|
||||
}
|
||||
@else if (formControl.hasError('completed')) {
|
||||
Diese Bestellung wurde bereits abgeschlossen
|
||||
}
|
||||
@else if (formControl.hasError('required')) {
|
||||
Bitte geben Sie Ihren Code ein
|
||||
}
|
||||
@else if (formControl.hasError('severalOrders')) {
|
||||
Mehrere Bestellungen gefunden - bitte kontaktieren Sie den Support
|
||||
}
|
||||
@else if (formControl.hasError('alreadyBooked')) {
|
||||
Diese Bestellung wurde bereits bezahlt
|
||||
}
|
||||
@else if (formControl.hasError('cancelled')) {
|
||||
Diese Bestellung wurde storniert
|
||||
}
|
||||
@else if (formControl.hasError('serverError')) {
|
||||
Fehler beim Laden der Bestellung
|
||||
}
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (formControl.valid || !formControl.touched) {
|
||||
<div class="h-6"></div>
|
||||
}
|
||||
<button mat-button class="w-100 mt-2" matButton="filled" color="accent" [disabled]="(loadingService.loading$ | async) || formControl.invalid" type="submit">Tickets jetzt online bezahlen</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
112
src/app/pay-for-order/pay-for-order.component.ts
Normal file
112
src/app/pay-for-order/pay-for-order.component.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Component, DestroyRef, inject, OnInit } from '@angular/core';
|
||||
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 { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pay-for-order',
|
||||
standalone: false,
|
||||
templateUrl: './pay-for-order.component.html',
|
||||
styleUrl: './pay-for-order.component.css',
|
||||
})
|
||||
export class PayForOrderComponent implements OnInit {
|
||||
private httpService = inject(HttpService);
|
||||
private router = inject(Router);
|
||||
private route = inject(ActivatedRoute);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
public loadingService = inject(LoadingService);
|
||||
|
||||
queryError?: string;
|
||||
|
||||
formControl = new FormControl('', {
|
||||
validators: [
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(6)
|
||||
]
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
const error = this.route.snapshot.queryParamMap.get('error');
|
||||
const code = this.route.snapshot.queryParamMap.get('code');
|
||||
|
||||
if (code) {
|
||||
this.formControl.setValue(code);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
// Warte einen Tick, damit Angular das FormControl initialisiert hat
|
||||
setTimeout(() => {
|
||||
this.formControl.clearValidators();
|
||||
this.formControl.setErrors({ [error]: true });
|
||||
this.formControl.markAsTouched();
|
||||
});
|
||||
|
||||
// Bei erster Änderung: Validatoren wieder aktivieren
|
||||
this.formControl.valueChanges.pipe(
|
||||
take(1),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(() => {
|
||||
this.formControl.setValidators([
|
||||
Validators.required,
|
||||
Validators.minLength(6),
|
||||
Validators.maxLength(6)
|
||||
]);
|
||||
this.formControl.updateValueAndValidity();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onInput(event: Event) {
|
||||
this.queryError = undefined;
|
||||
const input = event.target as HTMLInputElement;
|
||||
const filtered = input.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
||||
this.formControl.setValue(filtered, { emitEvent: false });
|
||||
}
|
||||
|
||||
DoSubmit() {
|
||||
this.formControl.markAsTouched();
|
||||
if (this.formControl.invalid) return;
|
||||
|
||||
const code = this.formControl.value?.trim();
|
||||
if (!code || code.length !== 6) return;
|
||||
|
||||
this.loadingService.show();
|
||||
const orderFilter = [`eq;code;string;${code}`];
|
||||
|
||||
this.httpService.getOrdersByFilter(orderFilter).pipe(
|
||||
map(orders => {
|
||||
this.loadingService.hide();
|
||||
if (orders.length === 0) {
|
||||
this.formControl.setErrors({ invalid: true });
|
||||
return
|
||||
}
|
||||
|
||||
if (orders.length > 1) {
|
||||
this.formControl.setErrors({ severalOrders: true });
|
||||
return;
|
||||
}
|
||||
const order = orders[0];
|
||||
if (order.booked) {
|
||||
this.formControl.setErrors({ alreadyBooked: true });
|
||||
return;
|
||||
}
|
||||
if (order.cancelled) {
|
||||
this.formControl.setErrors({ cancelled: true });
|
||||
return;
|
||||
}
|
||||
this.router.navigate(['/checkout/order', order.code]);
|
||||
}),
|
||||
catchError(err => {
|
||||
this.loadingService.hide();
|
||||
this.loadingService.showError(err);
|
||||
this.formControl.setErrors({ serverError: true });
|
||||
return of(null);
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe();
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
if (!tickets.length) {
|
||||
return from(this.router.navigate(
|
||||
['/checkout/order'],
|
||||
{ queryParams: { error: 'invalid' } }
|
||||
{ queryParams: { error: 'invalid', code: orderCode } }
|
||||
));
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
if (this.order.booked || this.order.cancelled) {
|
||||
return from(this.router.navigate(
|
||||
['/checkout/order'],
|
||||
{ queryParams: { error: 'completed' } }
|
||||
{ queryParams: { error: 'completed', code: orderCode } }
|
||||
));
|
||||
}
|
||||
|
||||
@@ -264,14 +264,16 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy {
|
||||
return this.loadPerformanceAndSeats();
|
||||
}),
|
||||
catchError(err => {
|
||||
this.loading.hide();
|
||||
console.error('Fehler beim Laden der Bestellung', err);
|
||||
|
||||
return from(this.router.navigate(
|
||||
['/checkout/order'],
|
||||
{ queryParams: { error: 'invalid' } }
|
||||
{ queryParams: { error: 'invalid', code: orderCode } }
|
||||
));
|
||||
}),
|
||||
finalize(() => {
|
||||
this.loading.hide();
|
||||
}),
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user