From 78144d74475d69ddd22731098908f0be844b7fb6 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Tue, 18 Nov 2025 21:00:29 +0100 Subject: [PATCH 1/2] Update routing and UI for performance checkout Changed route paths and parameters for performance checkout to use '/performance/:performanceId/checkout' instead of '/selection/performance/:id'. Updated related components to use new route and param names. Improved UI spacing and styling in several components for better layout and consistency. --- src/app/app-routing-module.ts | 2 +- .../movie-performance/movie-performance.component.html | 2 +- src/app/movie-performance/movie-performance.component.ts | 2 +- src/app/performance-info/performance-info.component.html | 2 +- src/app/purchase-failed/purchase-failed.component.html | 2 +- src/app/purchase-success/purchase-success.component.html | 6 +++--- .../reservation-failed/reservation-failed.component.html | 2 +- .../reservation-success.component.html | 8 +++----- src/app/theater-layout/theater-layout.component.html | 6 +++--- src/app/theater-overlay/theater-overlay.component.html | 6 +++--- src/app/theater-overlay/theater-overlay.component.ts | 4 +++- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index d22e9df..6f48c5e 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -27,7 +27,7 @@ const routes: Routes = [ canActivate: [AuthGuard], data: { roles: ['admin'] }, // Array von erlaubten Rollen. Derzeit gäbe es 'admin' und 'employee' }, - { path: 'selection/performance/:id', component: TheaterOverlayComponent}, + { path: 'performance/:performanceId/checkout', component: TheaterOverlayComponent}, ], }, diff --git a/src/app/movie-performance/movie-performance.component.html b/src/app/movie-performance/movie-performance.component.html index f1811e4..1584f95 100644 --- a/src/app/movie-performance/movie-performance.component.html +++ b/src/app/movie-performance/movie-performance.component.html @@ -1,6 +1,6 @@ -
+

{{ hall() }}

diff --git a/src/app/movie-performance/movie-performance.component.ts b/src/app/movie-performance/movie-performance.component.ts index b6925d0..6f98ff9 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 = `../selection/performance/${this.id()}`; + this.route = `../performance/${this.id()}/checkout`; } startTime = computed(() => diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html index 786c373..ee30310 100644 --- a/src/app/performance-info/performance-info.component.html +++ b/src/app/performance-info/performance-info.component.html @@ -13,7 +13,7 @@

{{ getStartTimeString() }} • {{ performance().hall.name }}

{{ movie().title }}

- +
diff --git a/src/app/purchase-failed/purchase-failed.component.html b/src/app/purchase-failed/purchase-failed.component.html index 6cc5888..d841ce0 100644 --- a/src/app/purchase-failed/purchase-failed.component.html +++ b/src/app/purchase-failed/purchase-failed.component.html @@ -6,6 +6,6 @@

Leider konnten Ihre Sitzplätze nicht gekauft werden. Dies kann passieren, wenn andere Nutzer zeitgleich versucht haben, dieselben Sitzplätze zu kaufen.

- + diff --git a/src/app/purchase-success/purchase-success.component.html b/src/app/purchase-success/purchase-success.component.html index 4d40726..c439043 100644 --- a/src/app/purchase-success/purchase-success.component.html +++ b/src/app/purchase-success/purchase-success.component.html @@ -2,10 +2,10 @@

Vielen Dank für Ihren Einkauf!

Ihre Sitzplätze wurden erfolgreich gebucht.

- + - - + + diff --git a/src/app/reservation-failed/reservation-failed.component.html b/src/app/reservation-failed/reservation-failed.component.html index a8913c0..abd1532 100644 --- a/src/app/reservation-failed/reservation-failed.component.html +++ b/src/app/reservation-failed/reservation-failed.component.html @@ -6,6 +6,6 @@

Leider konnten Ihre Sitzplätze nicht reserviert werden. Dies kann passieren, wenn andere Nutzer gleichzeitig versucht haben, dieselben Sitzplätze zu reservieren.

- + diff --git a/src/app/reservation-success/reservation-success.component.html b/src/app/reservation-success/reservation-success.component.html index 0a463a0..8d28e59 100644 --- a/src/app/reservation-success/reservation-success.component.html +++ b/src/app/reservation-success/reservation-success.component.html @@ -1,17 +1,15 @@

Reservierung erfolgreich!

-

Ihre Sitzplätze wurden erfolgreich reserviert. Bitte nennen sie den folgenden Code an der Kasse, um Ihre Reservierung in eine Buchung umzuwandeln.

{{ order().code }}
- - -
+ + +
Reservierung stornieren
- diff --git a/src/app/theater-layout/theater-layout.component.html b/src/app/theater-layout/theater-layout.component.html index 5637093..0563b21 100644 --- a/src/app/theater-layout/theater-layout.component.html +++ b/src/app/theater-layout/theater-layout.component.html @@ -3,12 +3,12 @@ Leinwand

-
+
@for (row of seatsPerRow(); track $index) {
-
+
@if ($index % 4 === 0) { speaker @@ -25,7 +25,7 @@ -
+
@if ($index % 4 === 0) { speaker diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html index 7a88cd2..78bd5b8 100644 --- a/src/app/theater-overlay/theater-overlay.component.html +++ b/src/app/theater-overlay/theater-overlay.component.html @@ -1,8 +1,8 @@ -
+
-
+
@if (!performance && (loading.loading$ | async)){
@@ -18,6 +18,6 @@
- +
diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index 68621eb..f588044 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -25,6 +25,7 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { readonly loading = inject(LoadingService); showId!: number; + orderId?: string; seatsPerRow = signal<{ seat: Sitzplatz | null, state: TheaterSeatState | null }[][]>([]); performance: Vorstellung | undefined; seatCategories: Sitzkategorie[] = []; @@ -33,7 +34,8 @@ export class TheaterOverlayComponent implements OnInit, OnDestroy { private isInitialLoad = signal(true); ngOnInit() { - this.showId = Number(this.route.snapshot.paramMap.get('id')!); + this.showId = Number(this.route.snapshot.paramMap.get('performanceId')!); + this.orderId = this.route.snapshot.queryParams['paramName']; this.selectedSeatService.clearSelection(); this.selectedSeatService.setSeatSelectable(true); From f4eb700ab49148ca050fbbc823c43890a39bd316 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Tue, 18 Nov 2025 23:00:18 +0100 Subject: [PATCH 2/2] Add zoom and device detection with warning component Introduces DeviceDetectionService and ZoomDetectionService to detect mobile devices and browser zoom level. Adds ZoomWarningComponent to display warnings for unsupported mobile devices and non-optimal browser zoom, and integrates it into the app layout. Updates routing to allow mobile access for the 'poc-model' route. --- src/app/app-module.ts | 2 + src/app/app-routing-module.ts | 2 +- src/app/app.html | 1 + src/app/device-detection.service.ts | 29 +++++ src/app/zoom-detection.service.ts | 33 ++++++ .../zoom-warning/zoom-warning.component.css | 14 +++ .../zoom-warning/zoom-warning.component.html | 81 ++++++++++++++ .../zoom-warning/zoom-warning.component.ts | 100 ++++++++++++++++++ 8 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/app/device-detection.service.ts create mode 100644 src/app/zoom-detection.service.ts create mode 100644 src/app/zoom-warning/zoom-warning.component.css create mode 100644 src/app/zoom-warning/zoom-warning.component.html create mode 100644 src/app/zoom-warning/zoom-warning.component.ts diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 2172c39..2794819 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -64,6 +64,7 @@ import { PurchaseSuccessComponent } from './purchase-success/purchase-success.co import { PurchaseFailedComponent } from './purchase-failed/purchase-failed.component'; import { TicketSmallComponent } from './ticket-small/ticket-small.component'; import { TicketListComponent } from './ticket-list/ticket-list.component'; +import { ZoomWarningComponent } from './zoom-warning/zoom-warning.component'; @NgModule({ @@ -107,6 +108,7 @@ import { TicketListComponent } from './ticket-list/ticket-list.component'; PurchaseFailedComponent, TicketSmallComponent, TicketListComponent, + ZoomWarningComponent, ], imports: [ AppRoutingModule, diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index 6f48c5e..e5692a4 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -12,7 +12,7 @@ import { AuthGuard } from './auth.guard'; const routes: Routes = [ // Seiten ohne Layout { path: 'landing', component: HomeComponent }, - { path: 'poc-model', component: PocModelComponent }, + { path: 'poc-model', component: PocModelComponent, data: { allowMobile: true } }, // Seiten mit MainLayout { diff --git a/src/app/app.html b/src/app/app.html index 67e7bd4..bc230ce 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1 +1,2 @@ + diff --git a/src/app/device-detection.service.ts b/src/app/device-detection.service.ts new file mode 100644 index 0000000..c4035ea --- /dev/null +++ b/src/app/device-detection.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DeviceDetectionService { + private _isMobile: boolean; + + constructor() { + this._isMobile = this.checkIfMobile(); + } + + isMobile(): boolean { + return this._isMobile; + } + + private checkIfMobile(): boolean { + const userAgent = navigator.userAgent.toLowerCase(); + const isMobileUA = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; + const isSmallScreen = window.innerWidth < 768; + + return isMobileUA || (isTouchDevice && isSmallScreen); + } + + recheckDevice(): void { + this._isMobile = this.checkIfMobile(); + } +} diff --git a/src/app/zoom-detection.service.ts b/src/app/zoom-detection.service.ts new file mode 100644 index 0000000..d9e3311 --- /dev/null +++ b/src/app/zoom-detection.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, fromEvent } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class ZoomDetectionService { + private zoomLevel$ = new BehaviorSubject(this.getZoomLevel()); + + constructor() { + // Zoom-Änderungen überwachen + fromEvent(window, 'resize') + .pipe(debounceTime(200)) + .subscribe(() => { + this.zoomLevel$.next(this.getZoomLevel()); + }); + } + + getZoomLevel(): number { + const devicePixelRatio = window.devicePixelRatio || 1; + return devicePixelRatio; + } + + getZoomLevel$() { + return this.zoomLevel$.asObservable(); + } + + isZoomOutOfRange(minZoom: number = 0.95, maxZoom: number = 1.05): boolean { + const currentZoom = this.getZoomLevel(); + return currentZoom < minZoom || currentZoom > maxZoom; + } +} diff --git a/src/app/zoom-warning/zoom-warning.component.css b/src/app/zoom-warning/zoom-warning.component.css new file mode 100644 index 0000000..21fcb9c --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.css @@ -0,0 +1,14 @@ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(20px) scale(0.9); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.zoom-info-box { + animation: slideIn 0.3s ease-out; +} diff --git a/src/app/zoom-warning/zoom-warning.component.html b/src/app/zoom-warning/zoom-warning.component.html new file mode 100644 index 0000000..ea79125 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.html @@ -0,0 +1,81 @@ +@if (isOutOfRange && !isDismissed && !isMobile) { +
+ + +
+ + screenshot_monitor + + + warning + +
+ +

Browser-Zoom nicht optimal!

+

Ihr Browser-Zoom ist auf {{ currentZoomPercentage }}% eingestellt.
Für die beste Darstellung empfehlen wir 100%.

+ +
+
+

Browser-Zoom zurücksetzen:

+
+

+ Strg + / + + + + 0 + (Windows, Linux / Mac) +

+
+
+ +
+

Windows-Skalierung prüfen:

+
    +
  1. Öffnen Sie die Windows-Einstellungen
  2. +
  3. Navigieren Sie zu SystemBildschirm
  4. +
  5. Setzen Sie unter "Skalierung" den Wert auf 100%
  6. +
+
+
+
+} + +@if (showMobileWarning) { +
+
+ +

InfiniMotion

+
+
+
+
+
+ + screenshot_monitor + + + warning + +
+ +

Nur am PC verfügbar

+ +

+ Diese Anwendung ist für die Nutzung am Desktop-PC optimiert und kann auf mobilen Geräten nicht verwendet werden. +

+ +
+

Deine derzeitige URL:

+

+ {{ currentUrl }} +

+
+
+
+} diff --git a/src/app/zoom-warning/zoom-warning.component.ts b/src/app/zoom-warning/zoom-warning.component.ts new file mode 100644 index 0000000..8802167 --- /dev/null +++ b/src/app/zoom-warning/zoom-warning.component.ts @@ -0,0 +1,100 @@ +import { DeviceDetectionService } from './../device-detection.service'; +import { Component, HostListener, inject, OnDestroy, OnInit } from '@angular/core'; +import { filter, Subject, takeUntil } from 'rxjs'; +import { ZoomDetectionService } from '../zoom-detection.service'; +import { NavigationEnd, Router } from '@angular/router'; + +@Component({ + selector: 'app-zoom-warning', + standalone: false, + templateUrl: './zoom-warning.component.html', + styleUrl: './zoom-warning.component.css', +}) +export class ZoomWarningComponent implements OnInit, OnDestroy { + currentZoomPercentage = 100; + isOutOfRange = false; + isDismissed = false; + isMobile = false; + showMobileWarning = false; + currentUrl = ''; + + private destroy$ = new Subject(); + private currentZoomLevel = 1; + private lastZoomLevel = 0; + + zoomDetectionService = inject(ZoomDetectionService); + deviceDetectionService = inject(DeviceDetectionService) + + constructor(private router: Router) { + this.isMobile = this.deviceDetectionService.isMobile(); + this.currentUrl = window.location.href; + this.checkIfShouldShowMobileWarning(); + } + + ngOnInit() { + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntil(this.destroy$) + ) + .subscribe(() => { + this.checkIfShouldShowMobileWarning(); + }); + + if (!this.isMobile) { + this.updateZoomInfo(); + + this.zoomDetectionService.getZoomLevel$() + .pipe(takeUntil(this.destroy$)) + .subscribe((newZoomLevel) => { + if (Math.abs(newZoomLevel - this.lastZoomLevel) > 0.01) { + this.lastZoomLevel = newZoomLevel; + this.isDismissed = false; + this.updateZoomInfo(); + } + }); + } + } + + private checkIfShouldShowMobileWarning() { + if (!this.isMobile) { + this.showMobileWarning = false; + return; + } + + const currentRoute = this.router.routerState.root; + const allowMobile = this.getRouteData(currentRoute, 'allowMobile'); + + this.showMobileWarning = !allowMobile; + } + + private getRouteData(route: any, key: string): any { + while (route) { + if (route.snapshot?.data?.[key] !== undefined) { + return route.snapshot.data[key]; + } + route = route.firstChild; + } + return false; + } + + private updateZoomInfo() { + this.currentZoomLevel = this.zoomDetectionService.getZoomLevel(); + this.currentZoomPercentage = Math.round(this.currentZoomLevel * 100); + this.isOutOfRange = this.zoomDetectionService.isZoomOutOfRange(); + } + + getCompensationTransform(): string { + const scale = 1 / this.currentZoomLevel; + return `scale(${scale})`; + } + + dismissWarning() { + this.isDismissed = true; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } +}