diff --git a/package-lock.json b/package-lock.json index 8cc2069..eba7cf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "html2canvas": "^1.4.1", "jspdf": "^3.0.4", "ngx-mask": "^20.0.3", + "ngx-scanner-qrcode": "^1.7.6", "postcss": "^8.5.6", "rxjs": "~7.8.0", "tailwindcss": "^4.1.14", @@ -446,7 +447,6 @@ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz", "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==", "license": "MIT", - "peer": true, "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" @@ -497,7 +497,6 @@ "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.13.tgz", "integrity": "sha512-Jy+Qu6760TZyiDJX0+fNzkc70+lwF9ojdkIyCso/Lvbx1v3Fki0+9Wui7Vge56hknkr05xXg1aEUeqMN0966Lg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -514,7 +513,6 @@ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.13.tgz", "integrity": "sha512-YEjzHxz9laEcC2YPBA7L09Ys8UIuPrRiBZcGCrOXzXmPATHGYuxqYuhZ8iKmKV0PG/4pP2fxD3Mv5wN0cBaOWg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -528,7 +526,6 @@ "integrity": "sha512-Cou3G8C60eKpD93SKBJRG5pa/xpmMHe6sc2aanWjneGWjZq1kR4v5eQwwr8LUByIsafcqxHGT7+q1bYXT2p2DQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "7.28.3", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -561,7 +558,6 @@ "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.13.tgz", "integrity": "sha512-12Kou+WAIjAUSG5TkDbypV2kreJ105VylAjlQ09bCvsGNTHjezGgahFa/tLz7iyrozhuivtGiQtiDaYsc79ysw==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -587,7 +583,6 @@ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.13.tgz", "integrity": "sha512-9vu9MCHJtgXvgPH+ZgXN46N3gpBBAckcmG62P7U+9BKivWvv3rEvkgX+4HvO+Pm2D6x/Jy1xbiQuVq9EDGPSNA==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -623,7 +618,6 @@ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.13.tgz", "integrity": "sha512-KyJzzpD4jMPGotDgVHF0cz9psjlVg6wYQrhuWcLeE97VUvp+CdwdOJ9tlxDlGE5tYZ0JrQxAT0l5qdcr6K9iNQ==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -690,7 +684,6 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1656,7 +1649,6 @@ "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^4.2.1", "@inquirer/confirm": "^5.1.14", @@ -3909,7 +3901,6 @@ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4208,37 +4199,28 @@ } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", + "integrity": "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==", "dev": true, "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", - "debug": "^4.4.0", + "debug": "^4.4.3", "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { "node": ">=18" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/boolbase": { @@ -4292,7 +4274,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -5473,7 +5454,6 @@ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -6473,8 +6453,7 @@ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz", "integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jiti": { "version": "2.6.1", @@ -6585,7 +6564,6 @@ "integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@colors/colors": "1.5.0", "body-parser": "^1.19.0", @@ -7302,7 +7280,6 @@ "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", @@ -7971,6 +7948,19 @@ "@angular/forms": ">=14.0.0" } }, + "node_modules/ngx-scanner-qrcode": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/ngx-scanner-qrcode/-/ngx-scanner-qrcode-1.7.6.tgz", + "integrity": "sha512-4AcRh+ozX0Arf97Xr1OmYRJUngHZDuU6b5pb9jsmM1Y/cpZX3rbI6mBQjsev65bm4UgDe+7naRgiVY07+K+vtw==", + "license": "LGPL-2.1+", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 || ^23.0.0 || ^24.0.0", + "@angular/core": "^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 || ^23.0.0 || ^24.0.0" + } + }, "node_modules/node-addon-api": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", @@ -9208,7 +9198,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -9244,7 +9233,6 @@ "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -10128,8 +10116,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/tuf-js": { "version": "3.1.0", @@ -10167,7 +10154,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10363,7 +10349,6 @@ "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -10766,7 +10751,6 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10785,8 +10769,7 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", - "license": "MIT", - "peer": true + "license": "MIT" } } } diff --git a/package.json b/package.json index 87d9144..a5ba11d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "html2canvas": "^1.4.1", "jspdf": "^3.0.4", "ngx-mask": "^20.0.3", + "ngx-scanner-qrcode": "^1.7.6", "postcss": "^8.5.6", "rxjs": "~7.8.0", "tailwindcss": "^4.1.14", diff --git a/src/app/app-module.ts b/src/app/app-module.ts index 6363aa9..e803c3e 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -27,7 +27,7 @@ import { MatBadgeModule } from '@angular/material/badge'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; -import {MatSelectModule} from '@angular/material/select'; +import { MatSelectModule } from '@angular/material/select'; import { MatSortModule } from '@angular/material/sort'; import { HeaderComponent } from './header/header.component'; @@ -79,7 +79,8 @@ import { PricelistComponent } from './pricelist/pricelist.component'; import { TheaterLayoutDesignerComponent } from './theater-layout-designer/theater-layout-designer.component'; import { PdfTicketComponent } from './pdf-ticket/pdf-ticket.component'; import { TestComponent } from './test/test.component'; - +import { TicketValidationComponent } from './ticket-validation/ticket-validation.component'; +import { TicketValidationResultComponent } from './ticket-validation-result/ticket-validation-result.component'; @NgModule({ @@ -133,6 +134,8 @@ import { TestComponent } from './test/test.component'; PricelistComponent, TheaterLayoutDesignerComponent, TestComponent, + TicketValidationComponent, + TicketValidationResultComponent, ], imports: [ AppRoutingModule, @@ -167,7 +170,7 @@ import { TestComponent } from './test/test.component'; MatTableModule, MatSelectModule, MatSortModule, - PdfTicketComponent + PdfTicketComponent, ], providers: [ provideBrowserGlobalErrorListeners(), @@ -178,4 +181,5 @@ import { TestComponent } from './test/test.component'; ], bootstrap: [App] }) -export class AppModule { } +export class AppModule { +} diff --git a/src/app/app-routing-module.ts b/src/app/app-routing-module.ts index 303391c..0ce4a72 100644 --- a/src/app/app-routing-module.ts +++ b/src/app/app-routing-module.ts @@ -60,7 +60,7 @@ const routes: Routes = [ path: 'employee/validation/ticket', component: TicketValidationComponent, canActivate: [AuthGuard], - data: { roles: ['employee'] }, + data: { roles: ['employee'], allowMobile: true }, }, { path: 'employee/validation/ticket/:ticketId', diff --git a/src/app/auth.guard.ts b/src/app/auth.guard.ts index 17f490b..c2af3a8 100644 --- a/src/app/auth.guard.ts +++ b/src/app/auth.guard.ts @@ -33,7 +33,7 @@ export class AuthGuard implements CanActivate { const dialogRef = this.dialog.open(LoginDialog, { disableClose: true, backdropClass: 'backdropBackground', - data: { user }, + data: { user, allowMobile: true }, }); const result = await firstValueFrom(dialogRef.afterClosed()); diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index fd0cffb..317c914 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -1,4 +1,4 @@ -import { AuthService, User, UserRole } from './../auth.service'; +import { AuthService, UserRole } from './../auth.service'; import { Component, inject, computed } from '@angular/core'; @Component({ @@ -13,9 +13,9 @@ export class NavbarComponent { { label: 'Preise', path: '/prices', auth: null }, { label: 'Bezahlen', path: '/checkout/order', auth: null }, { label: 'Einlasskontrolle', path: '/employee/validation/ticket', auth: ['employee'] }, - { label: 'Film importieren', path: '/admin/movie-importer', auth: ['admin']}, { label: 'Statistiken', path: '/admin/statistics', auth: ['admin'] }, { label: 'Saal-Designer', path: '/admin/designer', auth: ['admin'] }, + { label: 'Film-Importer', path: '/admin/movie-importer', auth: ['admin']}, ]; private auth = inject(AuthService); diff --git a/src/app/pay-for-order/pay-for-order.component.ts b/src/app/pay-for-order/pay-for-order.component.ts index 9ae834f..0daaf82 100644 --- a/src/app/pay-for-order/pay-for-order.component.ts +++ b/src/app/pay-for-order/pay-for-order.component.ts @@ -88,14 +88,14 @@ export class PayForOrderComponent implements OnInit { return; } const order = orders[0]; - if (order.booked) { - this.formControl.setErrors({ alreadyBooked: true }); - return; - } if (order.cancelled) { this.formControl.setErrors({ cancelled: true }); return; } + if (order.booked) { + this.formControl.setErrors({ alreadyBooked: true }); + return; + } this.router.navigate(['/checkout/order', order.code]); }), catchError(err => { diff --git a/src/app/purchase-success/purchase-success.component.html b/src/app/purchase-success/purchase-success.component.html index 8e986e3..2423084 100644 --- a/src/app/purchase-success/purchase-success.component.html +++ b/src/app/purchase-success/purchase-success.component.html @@ -2,7 +2,7 @@

Vielen Dank für Ihren Einkauf!

{{ infoText }}

- + diff --git a/src/app/reservation-success/reservation-success.component.html b/src/app/reservation-success/reservation-success.component.html index 590241e..d84264d 100644 --- a/src/app/reservation-success/reservation-success.component.html +++ b/src/app/reservation-success/reservation-success.component.html @@ -12,7 +12,7 @@ -
+
Reservierung stornieren
diff --git a/src/app/ticket-small/ticket-small.component.html b/src/app/ticket-small/ticket-small.component.html index 8d9d732..8ad4428 100644 --- a/src/app/ticket-small/ticket-small.component.html +++ b/src/app/ticket-small/ticket-small.component.html @@ -6,12 +6,7 @@

{{ ticket().seat.row.category.name }} - @if (ticket().seat.row.category.name.length > 10) { -
- } - @else { - • - } Reihe + • Reihe {{ convertIntoRowName(ticket().seat.row.position) }} Platz {{ ticket().seat.position }} diff --git a/src/app/ticket-validation-result/ticket-validation-result.component.css b/src/app/ticket-validation-result/ticket-validation-result.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/ticket-validation-result/ticket-validation-result.component.html b/src/app/ticket-validation-result/ticket-validation-result.component.html new file mode 100644 index 0000000..67e8981 --- /dev/null +++ b/src/app/ticket-validation-result/ticket-validation-result.component.html @@ -0,0 +1,118 @@ +@switch (result()) { + + @case ('nothing') { +

+ qr_code_scanner +

Bitte Ticket scannen
oder Ticketcode eingeben

+
+ } + + @case ('invalid') { +
+ cancel +

Ticket ungültig!

+

Unter der angegebenen Ticketnummer konnte keine gültige Eintrittskarte gefunden werden.

+
+ } + + @case ('unpaid') { + @if (performance()) { + +
+ } +
+ cancel +

Ticket nicht bezahlt!

+

Die Bestellung wurde noch nicht bezahlt und der Sitzplatz befindet sich noch im Status 'reserviert'.

+ @if (ticket()) { + + } + @if (order()) { +
+ Ticket jetzt bezahlen +
+ } +
+ } + + @case ('expired') { + @if (performance()) { + +
+ } +
+ cancel +

Vorstellung beendet!

+

Die Filmvorführung hat bereits stattgefunden.

+ @if (ticket()) { + + } +
+ } + + @case ('valid') { + @if (performance()) { + +
+ } +
+ check_circle +

Ticket gültig!

+ @if (ticket()) { + + } +
+ } + + @case ('early') { + @if (performance()) { + +
+ } +
+ schedule +

Noch kein Einlass!

+

Die Vorstellung beginnt in mehr als zwei Stunden.

+ @if (ticket()) { + + } +
+ } + + @case ('cancelled') { + @if (performance()) { + +
+ } +
+ cancel +

Ticket storniert!

+ @if (order()) { +

+ Die Bestellung wurde am {{ cancelledDate }} storniert. +

+ } + @if (ticket()) { + + } +
+ } + + @case ('error') { +
+ error +

Überprüfung fehlgeschlagen!

+

Bei der Validierung des Tickets ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.

+
+ } + + @default { +
+ +
+ } +} + diff --git a/src/app/ticket-validation-result/ticket-validation-result.component.ts b/src/app/ticket-validation-result/ticket-validation-result.component.ts new file mode 100644 index 0000000..cd4074e --- /dev/null +++ b/src/app/ticket-validation-result/ticket-validation-result.component.ts @@ -0,0 +1,22 @@ +import { Component, input } from '@angular/core'; +import { Bestellung, Eintrittskarte, Vorstellung } from '@infinimotion/model-frontend'; + +export type ValidationResult = 'nothing' | 'invalid' | 'unpaid' | 'expired' | 'valid' | 'early' | 'cancelled' | 'loading' | 'error'; + +@Component({ + selector: 'app-ticket-validation-result', + standalone: false, + templateUrl: './ticket-validation-result.component.html', + styleUrl: './ticket-validation-result.component.css', +}) +export class TicketValidationResultComponent { + result = input.required(); + performance = input(); + ticket = input(); + order = input(); + + get cancelledDate(): string { + if (!this.order()?.cancelled) return ''; + return new Date(this.order()?.cancelled!).toLocaleDateString(); + } +} diff --git a/src/app/ticket-validation/ticket-validation.component.css b/src/app/ticket-validation/ticket-validation.component.css new file mode 100644 index 0000000..dd13be1 --- /dev/null +++ b/src/app/ticket-validation/ticket-validation.component.css @@ -0,0 +1,7 @@ +.middle { + position: relative; + top: 45%; + -webkit-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} diff --git a/src/app/ticket-validation/ticket-validation.component.html b/src/app/ticket-validation/ticket-validation.component.html new file mode 100644 index 0000000..62d71dd --- /dev/null +++ b/src/app/ticket-validation/ticket-validation.component.html @@ -0,0 +1,61 @@ + + +
+
+
+
+
+ + Ticketnummer eingeben + + + +
+ + + + +
+ +
+
+ +
+
+ +
+
+
diff --git a/src/app/ticket-validation/ticket-validation.component.ts b/src/app/ticket-validation/ticket-validation.component.ts new file mode 100644 index 0000000..76016df --- /dev/null +++ b/src/app/ticket-validation/ticket-validation.component.ts @@ -0,0 +1,147 @@ +import { Component, DestroyRef, inject, OnInit } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; +import { HttpService } from '../http.service'; +import { LoadingService } from '../loading.service'; +import { catchError, map, Observable, of, take } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ValidationResult } from '../ticket-validation-result/ticket-validation-result.component'; +import { Bestellung, Eintrittskarte, Vorstellung } from '@infinimotion/model-frontend'; + +@Component({ + selector: 'app-ticket-validation', + standalone: false, + templateUrl: './ticket-validation.component.html', + styleUrl: './ticket-validation.component.css', +}) +export class TicketValidationComponent implements OnInit { + private httpService = inject(HttpService); + private route = inject(ActivatedRoute); + private destroyRef = inject(DestroyRef); + public loadingService = inject(LoadingService); + + queryError?: string; + + result: ValidationResult = 'nothing'; + performance?: Vorstellung; + ticket?: Eintrittskarte; + order?: Bestellung; + + public ticketPattern = { + 'X': { pattern: /[A-Za-z0-9]/ }, + 'T': { pattern: /[Tt]/ }, + }; + + formControl = new FormControl('', { + validators: [ + Validators.required, + Validators.minLength(8), + Validators.maxLength(8) + ] + }); + + 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) { + setTimeout(() => { + this.formControl.clearValidators(); + this.formControl.setErrors({ [error]: true }); + this.formControl.markAsTouched(); + }); + + this.formControl.valueChanges.pipe( + take(1), + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => { + this.formControl.setValidators([ + Validators.required, + Validators.minLength(8), + Validators.maxLength(8) + ]); + 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 !== 8) return; + + this.result = 'loading'; + this.loadingService.show(); + const ticketFilter = [`eq;code;string;${code}`]; + + this.httpService.getTicketsByFilter(ticketFilter).pipe( + map(tickets => { + this.loadingService.hide(); + + if (tickets.length === 0) { + this.result = 'invalid'; + this.formControl.setErrors({ invalid: true }); + return + } + if (tickets.length > 1) { + throw new Error("Für den Code existieren mehere Tickets"); + // this.formControl.setErrors({ severalTickets: true }); + return; + } + + this.ticket = tickets[0]; + this.order = this.ticket.order; + this.performance = this.ticket.show; + const now = new Date; + + if (this.ticket.order.cancelled) { + this.result = 'cancelled'; + return; + } + + const showStart = new Date(this.ticket.show.start) + const showEnd = new Date(showStart.getTime() + this.ticket.show.movie.duration * 60 * 1000); + if (showEnd < now) { + this.result = 'expired'; + return; + } + + if (this.ticket.order.booked === null) { + this.result = 'unpaid'; + return; + } + + const twoHoursInMs = 2 * 60 * 60 * 1000; + const twoHoursBeforeShow = new Date(showStart.getTime() - twoHoursInMs); + if (now < twoHoursBeforeShow) { + this.result = 'early'; + return; + } + + this.result = 'valid'; + }), + catchError(err => { + this.result = 'error'; + this.loadingService.hide(); + this.loadingService.showError(err); + // this.formControl.setErrors({ serverError: true }); + console.log(err); + return of(null); + }), + takeUntilDestroyed(this.destroyRef) + ).subscribe(); + } +} diff --git a/src/index.html b/src/index.html index 2df19e0..6f0646b 100644 --- a/src/index.html +++ b/src/index.html @@ -8,6 +8,7 @@ +