From f489073118ed41c056a69116d9718794e6313bda Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Wed, 12 Nov 2025 10:33:34 +0100
Subject: [PATCH 1/7] Add order and performance info components to ticket
overlay
Introduces OrderComponent, PerformanceInfoComponent, and ShoppingCartComponent for the ticket purchase flow. Updates theater-overlay to display seat selection alongside order details and performance info. Refactors seat and performance data loading, improves UI structure, and enhances movie info display components for consistency.
---
src/app/app-module.ts | 6 +++
.../menu-header/menu-header.component.html | 8 +++
src/app/menu-header/menu-header.component.ts | 2 +
.../movie-category.component.html | 2 +-
.../movie-duration.component.html | 6 ++-
.../movie-duration.component.ts | 4 ++
.../movie-poster/movie-poster.component.html | 6 +--
.../movie-rating/movie-rating.component.html | 2 +-
.../movie-rating/movie-rating.component.ts | 7 ++-
src/app/order/order.component.css | 3 ++
src/app/order/order.component.html | 49 +++++++++++++++++++
src/app/order/order.component.ts | 22 +++++++++
.../performance-info.component.css | 14 ++++++
.../performance-info.component.html | 21 ++++++++
.../performance-info.component.ts | 30 ++++++++++++
.../shopping-cart/shopping-cart.component.css | 0
.../shopping-cart.component.html | 1 +
.../shopping-cart/shopping-cart.component.ts | 11 +++++
.../theater-layout.component.css | 4 ++
.../theater-layout.component.html | 13 +++--
.../theater-overlay.component.html | 16 +++---
.../theater-overlay.component.ts | 46 +++++++++--------
22 files changed, 234 insertions(+), 39 deletions(-)
create mode 100644 src/app/order/order.component.css
create mode 100644 src/app/order/order.component.html
create mode 100644 src/app/order/order.component.ts
create mode 100644 src/app/performance-info/performance-info.component.css
create mode 100644 src/app/performance-info/performance-info.component.html
create mode 100644 src/app/performance-info/performance-info.component.ts
create mode 100644 src/app/shopping-cart/shopping-cart.component.css
create mode 100644 src/app/shopping-cart/shopping-cart.component.html
create mode 100644 src/app/shopping-cart/shopping-cart.component.ts
diff --git a/src/app/app-module.ts b/src/app/app-module.ts
index 3df7d00..140cb49 100644
--- a/src/app/app-module.ts
+++ b/src/app/app-module.ts
@@ -49,6 +49,9 @@ import { MovieImporterComponent } from './movie-importer/movie-importer.componen
import { MovieImportNoSearchResultComponent } from './movie-import-no-search-result/movie-import-no-search-result.component';
import { MovieImportSearchInfoComponent } from './movie-import-search-info/movie-import-search-info.component';
import { LoginDialog } from './login/login.dialog';
+import { PerformanceInfoComponent } from './performance-info/performance-info.component';
+import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component';
+import { OrderComponent } from './order/order.component';
@NgModule({
@@ -81,6 +84,9 @@ import { LoginDialog } from './login/login.dialog';
MovieImportNoSearchResultComponent,
MovieImportSearchInfoComponent,
LoginDialog,
+ PerformanceInfoComponent,
+ ShoppingCartComponent,
+ OrderComponent,
],
imports: [
AppRoutingModule,
diff --git a/src/app/menu-header/menu-header.component.html b/src/app/menu-header/menu-header.component.html
index 1941a29..beb2792 100644
--- a/src/app/menu-header/menu-header.component.html
+++ b/src/app/menu-header/menu-header.component.html
@@ -10,5 +10,13 @@
@if ( searchBar() ) {
}
+ @if ( backToSchedule() ) {
+
+ }
diff --git a/src/app/menu-header/menu-header.component.ts b/src/app/menu-header/menu-header.component.ts
index 18155ff..4dd4ec2 100644
--- a/src/app/menu-header/menu-header.component.ts
+++ b/src/app/menu-header/menu-header.component.ts
@@ -12,4 +12,6 @@ export class MenuHeaderComponent {
searchBar = input(false);
movieSearchResult = output();
+
+ backToSchedule = input(false);
}
diff --git a/src/app/movie-category/movie-category.component.html b/src/app/movie-category/movie-category.component.html
index 72757f5..3ff5e5c 100644
--- a/src/app/movie-category/movie-category.component.html
+++ b/src/app/movie-category/movie-category.component.html
@@ -1,3 +1,3 @@
-
+
{{ category() }}
diff --git a/src/app/movie-duration/movie-duration.component.html b/src/app/movie-duration/movie-duration.component.html
index 73845a3..b42ea62 100644
--- a/src/app/movie-duration/movie-duration.component.html
+++ b/src/app/movie-duration/movie-duration.component.html
@@ -1,4 +1,6 @@
-
-
+
+ @if (showIcon()) {
+
+ }
{{ durationText() }}
diff --git a/src/app/movie-duration/movie-duration.component.ts b/src/app/movie-duration/movie-duration.component.ts
index e029d3e..0bc94b6 100644
--- a/src/app/movie-duration/movie-duration.component.ts
+++ b/src/app/movie-duration/movie-duration.component.ts
@@ -8,9 +8,13 @@ import { Component, input, computed } from '@angular/core';
})
export class MovieDurationComponent {
duration = input(0);
+ showIcon = input(true);
durationText = computed(() => {
if (this.duration() > 0) {
+ if (!this.showIcon()) {
+ return `${this.duration()} Minuten`;
+ }
return `${this.duration()} Min.`;
}
return 'N/A';
diff --git a/src/app/movie-poster/movie-poster.component.html b/src/app/movie-poster/movie-poster.component.html
index 1378eab..c038336 100644
--- a/src/app/movie-poster/movie-poster.component.html
+++ b/src/app/movie-poster/movie-poster.component.html
@@ -7,7 +7,7 @@
>
diff --git a/src/app/movie-rating/movie-rating.component.html b/src/app/movie-rating/movie-rating.component.html
index a8e4c76..9427cf9 100644
--- a/src/app/movie-rating/movie-rating.component.html
+++ b/src/app/movie-rating/movie-rating.component.html
@@ -1,3 +1,3 @@
-
+
{{ ratingText() }}
diff --git a/src/app/movie-rating/movie-rating.component.ts b/src/app/movie-rating/movie-rating.component.ts
index 60951ab..8dfb344 100644
--- a/src/app/movie-rating/movie-rating.component.ts
+++ b/src/app/movie-rating/movie-rating.component.ts
@@ -1,4 +1,4 @@
-import { Component, input, computed } from '@angular/core';
+import { Component, input, computed, HostBinding } from '@angular/core';
@Component({
selector: 'app-movie-rating',
@@ -7,6 +7,11 @@ import { Component, input, computed } from '@angular/core';
styleUrl: './movie-rating.component.css'
})
export class MovieRatingComponent {
+
+ @HostBinding('class') get hostClasses(): string {
+ return this.ratingColor();
+ }
+
rating = input(0);
ratingColor = computed(() => {
diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css
new file mode 100644
index 0000000..9008a3b
--- /dev/null
+++ b/src/app/order/order.component.css
@@ -0,0 +1,3 @@
+mat-stepper {
+ background: transparent !important;
+}
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
new file mode 100644
index 0000000..3d94d59
--- /dev/null
+++ b/src/app/order/order.component.html
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+-
+
+ Zahlung
+ You are now done.
+
+
+
+
+
+
+
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts
new file mode 100644
index 0000000..330041b
--- /dev/null
+++ b/src/app/order/order.component.ts
@@ -0,0 +1,22 @@
+import { Vorstellung } from '@infinimotion/model-frontend';
+import { Component, inject, input } from '@angular/core';
+import { FormBuilder, Validators } from '@angular/forms';
+
+@Component({
+ selector: 'app-order',
+ standalone: false,
+ templateUrl: './order.component.html',
+ styleUrl: './order.component.css'
+})
+export class OrderComponent {
+ performance = input();
+
+ private _formBuilder = inject(FormBuilder);
+
+ firstFormGroup = this._formBuilder.group({
+ firstCtrl: ['', Validators.required],
+ });
+ secondFormGroup = this._formBuilder.group({
+ secondCtrl: ['', Validators.required],
+ });
+}
diff --git a/src/app/performance-info/performance-info.component.css b/src/app/performance-info/performance-info.component.css
new file mode 100644
index 0000000..69e9094
--- /dev/null
+++ b/src/app/performance-info/performance-info.component.css
@@ -0,0 +1,14 @@
+.info-box {
+ color: var(--mat-sys-on-surface);
+}
+
+::ng-deep .mat-step-header .mat-step-icon {
+ background-color: #ccc;
+ color: white;
+}
+
+
+::ng-deep .mat-step-header .mat-step-icon-selected,
+::ng-deep .mat-step-icon-state-edit {
+ background-color: var(--color-indigo-500) !important;
+}
diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html
new file mode 100644
index 0000000..4ed4613
--- /dev/null
+++ b/src/app/performance-info/performance-info.component.html
@@ -0,0 +1,21 @@
+
+
+
+
![Movie Poster]()
+
+
+
+
{{ getStartTimeString() }} • {{ performance().hall.name }}
+
{{ movie().title }}
+
+
+
+
diff --git a/src/app/performance-info/performance-info.component.ts b/src/app/performance-info/performance-info.component.ts
new file mode 100644
index 0000000..bc35f8b
--- /dev/null
+++ b/src/app/performance-info/performance-info.component.ts
@@ -0,0 +1,30 @@
+import { Component, computed, input } from '@angular/core';
+import { Vorstellung } from '@infinimotion/model-frontend';
+
+@Component({
+ selector: 'app-performance-info',
+ standalone: false,
+ templateUrl: './performance-info.component.html',
+ styleUrl: './performance-info.component.css'
+})
+export class PerformanceInfoComponent {
+ performance = input.required();
+
+ getStartTimeString(): string {
+ const date = new Date(this.performance().start);
+ return date.toLocaleDateString('de-DE', { weekday: 'short' }) + '. ' + date.toLocaleDateString('de-DE') + ', ' + date.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) + ' Uhr';
+ }
+
+ movie() {
+ return this.performance().movie
+ }
+
+ onPosterError(event: Event) {
+ const img = event.target as HTMLImageElement;
+ const placeholder = 'assets/poster_placeholder.png';
+
+ if (img.src !== window.location.origin + placeholder) {
+ img.src = placeholder;
+ }
+ }
+}
diff --git a/src/app/shopping-cart/shopping-cart.component.css b/src/app/shopping-cart/shopping-cart.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shopping-cart/shopping-cart.component.html b/src/app/shopping-cart/shopping-cart.component.html
new file mode 100644
index 0000000..5aff33e
--- /dev/null
+++ b/src/app/shopping-cart/shopping-cart.component.html
@@ -0,0 +1 @@
+shopping-cart works!
diff --git a/src/app/shopping-cart/shopping-cart.component.ts b/src/app/shopping-cart/shopping-cart.component.ts
new file mode 100644
index 0000000..382aba1
--- /dev/null
+++ b/src/app/shopping-cart/shopping-cart.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-shopping-cart',
+ standalone: false,
+ templateUrl: './shopping-cart.component.html',
+ styleUrl: './shopping-cart.component.css'
+})
+export class ShoppingCartComponent {
+
+}
diff --git a/src/app/theater-layout/theater-layout.component.css b/src/app/theater-layout/theater-layout.component.css
index e69de29..055c8c8 100644
--- a/src/app/theater-layout/theater-layout.component.css
+++ b/src/app/theater-layout/theater-layout.component.css
@@ -0,0 +1,4 @@
+:host {
+border-radius: 0.5rem;
+ background-color: white;
+}
diff --git a/src/app/theater-layout/theater-layout.component.html b/src/app/theater-layout/theater-layout.component.html
index a864e27..cd0560d 100644
--- a/src/app/theater-layout/theater-layout.component.html
+++ b/src/app/theater-layout/theater-layout.component.html
@@ -1,3 +1,10 @@
-@for (row of seatsPerRow(); track $index) {
-
-}
+
+
+ @for (row of seatsPerRow(); track $index) {
+
+ }
+
diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html
index bb9e257..d709d18 100644
--- a/src/app/theater-overlay/theater-overlay.component.html
+++ b/src/app/theater-overlay/theater-overlay.component.html
@@ -1,9 +1,9 @@
-
-
+
+
diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts
index a87cb30..c8a0e35 100644
--- a/src/app/theater-overlay/theater-overlay.component.ts
+++ b/src/app/theater-overlay/theater-overlay.component.ts
@@ -1,8 +1,8 @@
import {Component, inject, OnInit} from '@angular/core';
import {HttpService} from '../http.service';
import {LoadingService} from '../loading.service';
-import {catchError, of, tap} from 'rxjs';
-import {Sitzplatz} from '@infinimotion/model-frontend';
+import {catchError, forkJoin, of, tap} from 'rxjs';
+import {Sitzplatz, Vorstellung} from '@infinimotion/model-frontend';
import {TheaterSeatState} from '../model/theater-seat-state.model';
import {ActivatedRoute} from '@angular/router';
@@ -18,28 +18,34 @@ export class TheaterOverlayComponent implements OnInit {
showId!: number;
seatsPerRow: { seat: Sitzplatz, state: TheaterSeatState }[][] = []
+ performance: Vorstellung | undefined;
constructor(private route: ActivatedRoute) {}
- ngOnInit() {
- this.showId = Number(this.route.snapshot.paramMap.get('id')!);
- this.loadShowSeats();
- }
+ngOnInit() {
+ this.showId = Number(this.route.snapshot.paramMap.get('id')!);
+ this.loadPerformanceAndSeats();
+}
- loadShowSeats() {
- this.loading.show();
- this.http.getSeatsByShowId(this.showId).pipe(
- tap((data) => {
- this.seatsPerRow = this.converter(data)
- this.loading.hide();
- }),
- catchError(err => {
- this.loading.showError(err);
- console.error('Fehler beim Laden der Vorstellung', err);
- return of([]);
- })
- ).subscribe();
- }
+loadPerformanceAndSeats() {
+ this.loading.show();
+
+ forkJoin({
+ performance: this.http.getPerformaceById(this.showId),
+ seats: this.http.getSeatsByShowId(this.showId)
+ }).pipe(
+ tap(({ performance, seats }) => {
+ this.performance = performance;
+ this.seatsPerRow = this.converter(seats);
+ this.loading.hide();
+ }),
+ catchError(err => {
+ this.loading.showError(err);
+ console.error('Fehler beim Laden', err);
+ return of({ performance: null, seats: [] });
+ })
+ ).subscribe();
+}
converter(resp: { seats: Sitzplatz[], reserved: Sitzplatz[], booked: Sitzplatz[] }): {
seat: Sitzplatz,
From 8e4a024299afc5afaf4ca18eb3d4a2b8ed5d6f08 Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Wed, 12 Nov 2025 23:21:51 +0100
Subject: [PATCH 2/7] Get custom theme colors working
Introduces _theme-colors.scss with generated palettes and integrates them into custom-theme.scss for both light and dark themes. Removes stepper icon styles from performance-info.component.css and moves them to custom-theme.scss for better maintainability and consistency.
---
src/_theme-colors.scss | 137 ++++++++++++++++++
.../performance-info.component.css | 11 --
src/custom-theme.scss | 40 +++--
3 files changed, 167 insertions(+), 21 deletions(-)
create mode 100644 src/_theme-colors.scss
diff --git a/src/_theme-colors.scss b/src/_theme-colors.scss
new file mode 100644
index 0000000..5c6e044
--- /dev/null
+++ b/src/_theme-colors.scss
@@ -0,0 +1,137 @@
+// This file was generated by running 'ng generate @angular/material:theme-color'.
+// Proceed with caution if making changes to this file.
+
+@use 'sass:map';
+@use '@angular/material' as mat;
+
+// Note: Color palettes are generated from primary: 6366f1, tertiary: dd2979
+$_palettes: (
+ primary: (
+ 0: #000000,
+ 10: #07006c,
+ 20: #1000a9,
+ 25: #201cb4,
+ 30: #2f2ebe,
+ 35: #3c3dca,
+ 40: #494bd6,
+ 50: #6366f1,
+ 60: #8083ff,
+ 70: #a0a3ff,
+ 80: #c0c1ff,
+ 90: #e1e0ff,
+ 95: #f2efff,
+ 98: #fcf8ff,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ secondary: (
+ 0: #000000,
+ 10: #13144a,
+ 20: #292a60,
+ 25: #34366c,
+ 30: #404178,
+ 35: #4b4d85,
+ 40: #575992,
+ 50: #7072ac,
+ 60: #8a8bc8,
+ 70: #a5a6e4,
+ 80: #c0c1ff,
+ 90: #e1e0ff,
+ 95: #f2efff,
+ 98: #fcf8ff,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ tertiary: (
+ 0: #000000,
+ 10: #3f001c,
+ 20: #650031,
+ 25: #79003d,
+ 30: #8e0048,
+ 35: #a40054,
+ 40: #ba0060,
+ 50: #dd2979,
+ 60: #ff4993,
+ 70: #ff84ad,
+ 80: #ffb1c7,
+ 90: #ffd9e2,
+ 95: #ffecef,
+ 98: #fff8f8,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ neutral: (
+ 0: #000000,
+ 10: #1b1b23,
+ 20: #303038,
+ 25: #3b3a44,
+ 30: #46464f,
+ 35: #52515b,
+ 40: #5e5d67,
+ 50: #777680,
+ 60: #918f9a,
+ 70: #acaab5,
+ 80: #c7c5d1,
+ 90: #e4e1ed,
+ 95: #f2effb,
+ 98: #fcf8ff,
+ 99: #fffbff,
+ 100: #ffffff,
+ 4: #0d0d15,
+ 6: #13131b,
+ 12: #1f1f27,
+ 17: #292932,
+ 22: #34343d,
+ 24: #393841,
+ 87: #dbd8e4,
+ 92: #e9e6f3,
+ 94: #efecf8,
+ 96: #f5f2fe,
+ ),
+ neutral-variant: (
+ 0: #000000,
+ 10: #1a1a28,
+ 20: #2f2f3d,
+ 25: #3a3a49,
+ 30: #464554,
+ 35: #515160,
+ 40: #5d5d6d,
+ 50: #767586,
+ 60: #908fa0,
+ 70: #aba9bb,
+ 80: #c7c4d7,
+ 90: #e3e0f3,
+ 95: #f2efff,
+ 98: #fcf8ff,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+ error: (
+ 0: #000000,
+ 10: #410002,
+ 20: #690005,
+ 25: #7e0007,
+ 30: #93000a,
+ 35: #a80710,
+ 40: #ba1a1a,
+ 50: #de3730,
+ 60: #ff5449,
+ 70: #ff897d,
+ 80: #ffb4ab,
+ 90: #ffdad6,
+ 95: #ffedea,
+ 98: #fff8f7,
+ 99: #fffbff,
+ 100: #ffffff,
+ ),
+);
+
+$_rest: (
+ secondary: map.get($_palettes, secondary),
+ neutral: map.get($_palettes, neutral),
+ neutral-variant: map.get($_palettes, neutral-variant),
+ error: map.get($_palettes, error),
+);
+
+$primary-palette: map.merge(map.get($_palettes, primary), $_rest);
+$tertiary-palette: map.merge(map.get($_palettes, tertiary), $_rest);
\ No newline at end of file
diff --git a/src/app/performance-info/performance-info.component.css b/src/app/performance-info/performance-info.component.css
index 69e9094..28d36f0 100644
--- a/src/app/performance-info/performance-info.component.css
+++ b/src/app/performance-info/performance-info.component.css
@@ -1,14 +1,3 @@
.info-box {
color: var(--mat-sys-on-surface);
}
-
-::ng-deep .mat-step-header .mat-step-icon {
- background-color: #ccc;
- color: white;
-}
-
-
-::ng-deep .mat-step-header .mat-step-icon-selected,
-::ng-deep .mat-step-icon-state-edit {
- background-color: var(--color-indigo-500) !important;
-}
diff --git a/src/custom-theme.scss b/src/custom-theme.scss
index 5615bb5..3de7b38 100644
--- a/src/custom-theme.scss
+++ b/src/custom-theme.scss
@@ -5,6 +5,33 @@
// Learn more about theming and how to use it for your application's
// custom components at https://material.angular.dev/guide/theming
@use '@angular/material' as mat;
+@use './_theme-colors' as theme-colors;
+
+// Light Theme
+html {
+ color-scheme: light;
+ @include mat.theme((
+ color: (
+ theme-type: light,
+ primary: theme-colors.$primary-palette,
+ tertiary: theme-colors.$tertiary-palette,
+ ),
+ ));
+}
+
+// Dark Theme
+html.dark {
+ color-scheme: dark;
+ @include mat.theme((
+ color: (
+ theme-type: dark,
+ primary: theme-colors.$primary-palette,
+ tertiary: theme-colors.$tertiary-palette,
+ ),
+ ));
+}
+
+
@include mat.progress-bar-overrides((
active-indicator-color: white,
@@ -34,16 +61,9 @@
backdrop-filter: blur(2px);
}
-
-html {
- @include mat.theme((
- color: (
- primary: mat.$azure-palette,
- tertiary: mat.$blue-palette,
- ),
- typography: Roboto,
- density: 0,
- ));
+.mat-step-header .mat-step-icon:not(.mat-step-icon-selected):not(.mat-step-icon-completed) {
+ background-color: #ccc;
+ color: white;
}
body {
From db0322d44337204c98154451d6ce36fe6c9323de Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Thu, 13 Nov 2025 01:48:28 +0100
Subject: [PATCH 3/7] Add seat selection and no seats components
Introduces SeatSelectionComponent and NoSeatsInHallComponent for improved seat category display and handling cases with no available seats. Updates order flow to show seat categories, loading spinner, and total price. Refactors TheaterOverlayComponent to provide seat categories, and updates styles and dependencies.
---
package-lock.json | 8 ++--
package.json | 2 +-
src/app/app-module.ts | 6 +++
.../no-seats-in-hall.component.css | 0
.../no-seats-in-hall.component.html | 7 +++
.../no-seats-in-hall.component.ts | 11 +++++
src/app/order/order.component.css | 4 ++
src/app/order/order.component.html | 47 +++++++++++++++++--
src/app/order/order.component.ts | 5 +-
.../performance-info.component.html | 4 +-
.../seat-selection.component.css | 3 ++
.../seat-selection.component.html | 11 +++++
.../seat-selection.component.ts | 18 +++++++
.../theater-overlay.component.html | 2 +-
.../theater-overlay.component.ts | 18 ++++++-
src/index.html | 1 +
16 files changed, 131 insertions(+), 16 deletions(-)
create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.css
create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.html
create mode 100644 src/app/no-seats-in-hall/no-seats-in-hall.component.ts
create mode 100644 src/app/seat-selection/seat-selection.component.css
create mode 100644 src/app/seat-selection/seat-selection.component.html
create mode 100644 src/app/seat-selection/seat-selection.component.ts
diff --git a/package-lock.json b/package-lock.json
index ea5c339..0002880 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"@angular/material": "^20.2.9",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
- "@infinimotion/model-frontend": "^0.0.85",
+ "@infinimotion/model-frontend": "^0.0.89",
"@tailwindcss/postcss": "^4.1.14",
"postcss": "^8.5.6",
"rxjs": "~7.8.0",
@@ -1298,9 +1298,9 @@
}
},
"node_modules/@infinimotion/model-frontend": {
- "version": "0.0.85",
- "resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.85/model-frontend-0.0.85.tgz",
- "integrity": "sha512-QPiZNl//Y1JdxtXk+VScc67h1K664z68PUCXRff9fRf4IHlYXtqutc+ainK8vxOVSqqL6EEmDAtbLsRwrG6kRg==",
+ "version": "0.0.89",
+ "resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.89/model-frontend-0.0.89.tgz",
+ "integrity": "sha512-lvvQy8RWs41Bz52uBgsUKkwn8teGlgxlmG8Rvsgkh+v1IMVWFWVQmfMS7Rznd0lCZRgK1ByihH80X9eAN12idA==",
"license": "ISC"
},
"node_modules/@inquirer/ansi": {
diff --git a/package.json b/package.json
index 5027aa3..a8410ad 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"@angular/material": "^20.2.9",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
- "@infinimotion/model-frontend": "^0.0.85",
+ "@infinimotion/model-frontend": "^0.0.89",
"@tailwindcss/postcss": "^4.1.14",
"postcss": "^8.5.6",
"rxjs": "~7.8.0",
diff --git a/src/app/app-module.ts b/src/app/app-module.ts
index 140cb49..7bdff2c 100644
--- a/src/app/app-module.ts
+++ b/src/app/app-module.ts
@@ -12,6 +12,7 @@ import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatProgressBarModule } from '@angular/material/progress-bar';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
@@ -52,6 +53,8 @@ import { LoginDialog } from './login/login.dialog';
import { PerformanceInfoComponent } from './performance-info/performance-info.component';
import { ShoppingCartComponent } from './shopping-cart/shopping-cart.component';
import { OrderComponent } from './order/order.component';
+import { SeatSelectionComponent } from './seat-selection/seat-selection.component';
+import { NoSeatsInHallComponent } from './no-seats-in-hall/no-seats-in-hall.component';
@NgModule({
@@ -87,6 +90,8 @@ import { OrderComponent } from './order/order.component';
PerformanceInfoComponent,
ShoppingCartComponent,
OrderComponent,
+ SeatSelectionComponent,
+ NoSeatsInHallComponent,
],
imports: [
AppRoutingModule,
@@ -98,6 +103,7 @@ import { OrderComponent } from './order/order.component';
MatTabsModule,
MatToolbarModule,
MatProgressBarModule,
+ MatProgressSpinnerModule,
MatSnackBarModule,
MatAutocompleteModule,
MatInputModule,
diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.css b/src/app/no-seats-in-hall/no-seats-in-hall.component.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.html b/src/app/no-seats-in-hall/no-seats-in-hall.component.html
new file mode 100644
index 0000000..08cea86
--- /dev/null
+++ b/src/app/no-seats-in-hall/no-seats-in-hall.component.html
@@ -0,0 +1,7 @@
+
+
+ brightness_alert
+
+
Huch?! Keine Sitzplätze?
+
Hast du ein Glück, Stehplätze sind kostenlos.
+
diff --git a/src/app/no-seats-in-hall/no-seats-in-hall.component.ts b/src/app/no-seats-in-hall/no-seats-in-hall.component.ts
new file mode 100644
index 0000000..bf6ce88
--- /dev/null
+++ b/src/app/no-seats-in-hall/no-seats-in-hall.component.ts
@@ -0,0 +1,11 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-no-seats-in-hall',
+ standalone: false,
+ templateUrl: './no-seats-in-hall.component.html',
+ styleUrl: './no-seats-in-hall.component.css'
+})
+export class NoSeatsInHallComponent {
+
+}
diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css
index 9008a3b..b19ef5b 100644
--- a/src/app/order/order.component.css
+++ b/src/app/order/order.component.css
@@ -1,3 +1,7 @@
mat-stepper {
background: transparent !important;
}
+
+::ng-deep .mat-horizontal-stepper-header{
+ pointer-events: none !important;
+}
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
index 3d94d59..4f31b79 100644
--- a/src/app/order/order.component.html
+++ b/src/app/order/order.component.html
@@ -1,4 +1,15 @@
+
+@if (loadingService.loading$ | async){
+
+
+
+}
+@else if (performance()) {
+
Warenkorb
- @if (performance()) {
-
- }
+
-
-
+
+
+ @for (seatCategory of seatCategories(); track $index) {
+
+
+ }
+ @empty {
+
+ }
+
+
+
+
+
+
+
+
+ Tickets gesamt:
+
+
+ 75,00 €
+
+
+
+
+
+
@@ -46,4 +82,5 @@
+}
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts
index 330041b..3f3b094 100644
--- a/src/app/order/order.component.ts
+++ b/src/app/order/order.component.ts
@@ -1,4 +1,5 @@
-import { Vorstellung } from '@infinimotion/model-frontend';
+import { LoadingService } from './../loading.service';
+import { Sitzkategorie, Vorstellung } from '@infinimotion/model-frontend';
import { Component, inject, input } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
@@ -10,8 +11,10 @@ import { FormBuilder, Validators } from '@angular/forms';
})
export class OrderComponent {
performance = input();
+ seatCategories = input.required();
private _formBuilder = inject(FormBuilder);
+ loadingService = inject(LoadingService);
firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required],
diff --git a/src/app/performance-info/performance-info.component.html b/src/app/performance-info/performance-info.component.html
index 4ed4613..06435e9 100644
--- a/src/app/performance-info/performance-info.component.html
+++ b/src/app/performance-info/performance-info.component.html
@@ -11,9 +11,9 @@
{{ getStartTimeString() }} • {{ performance().hall.name }}
-
{{ movie().title }}
+
{{ movie().title }}
diff --git a/src/app/seat-selection/seat-selection.component.css b/src/app/seat-selection/seat-selection.component.css
new file mode 100644
index 0000000..c206bd7
--- /dev/null
+++ b/src/app/seat-selection/seat-selection.component.css
@@ -0,0 +1,3 @@
+.seat-name {
+ color: var(--mat-sys-primary);
+}
diff --git a/src/app/seat-selection/seat-selection.component.html b/src/app/seat-selection/seat-selection.component.html
new file mode 100644
index 0000000..853700b
--- /dev/null
+++ b/src/app/seat-selection/seat-selection.component.html
@@ -0,0 +1,11 @@
+{{ seatCategory().name }}
+
+
+
+ {{ seatCategory().icon }}
+
+
{{ getPriceDisplay(seatCategory().price) }}
+
+
× 2
+
25.00 €
+
diff --git a/src/app/seat-selection/seat-selection.component.ts b/src/app/seat-selection/seat-selection.component.ts
new file mode 100644
index 0000000..e9f0f8d
--- /dev/null
+++ b/src/app/seat-selection/seat-selection.component.ts
@@ -0,0 +1,18 @@
+import { Component, input } from '@angular/core';
+import { Sitzkategorie } from '@infinimotion/model-frontend';
+
+@Component({
+ selector: 'app-seat-selection',
+ standalone: false,
+ templateUrl: './seat-selection.component.html',
+ styleUrl: './seat-selection.component.css'
+})
+export class SeatSelectionComponent {
+ seatCategory = input.required();
+ amount: number = 1;
+
+ getPriceDisplay(price: number): string {
+ return `${(price / 100).toFixed(2)} €`;
+ }
+}
+
diff --git a/src/app/theater-overlay/theater-overlay.component.html b/src/app/theater-overlay/theater-overlay.component.html
index d709d18..c4c736e 100644
--- a/src/app/theater-overlay/theater-overlay.component.html
+++ b/src/app/theater-overlay/theater-overlay.component.html
@@ -4,6 +4,6 @@
-
+
diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts
index c8a0e35..0052b0a 100644
--- a/src/app/theater-overlay/theater-overlay.component.ts
+++ b/src/app/theater-overlay/theater-overlay.component.ts
@@ -2,7 +2,7 @@ import {Component, inject, OnInit} from '@angular/core';
import {HttpService} from '../http.service';
import {LoadingService} from '../loading.service';
import {catchError, forkJoin, of, tap} from 'rxjs';
-import {Sitzplatz, Vorstellung} from '@infinimotion/model-frontend';
+import {Sitzkategorie, Sitzplatz, Vorstellung} from '@infinimotion/model-frontend';
import {TheaterSeatState} from '../model/theater-seat-state.model';
import {ActivatedRoute} from '@angular/router';
@@ -19,6 +19,7 @@ export class TheaterOverlayComponent implements OnInit {
showId!: number;
seatsPerRow: { seat: Sitzplatz, state: TheaterSeatState }[][] = []
performance: Vorstellung | undefined;
+ seatCategories: Sitzkategorie[] = [];
constructor(private route: ActivatedRoute) {}
@@ -52,13 +53,26 @@ loadPerformanceAndSeats() {
state: TheaterSeatState
}[][] {
let rows: { seat: Sitzplatz, state: TheaterSeatState }[][] = [];
+ const categoryMap = new Map();
+
resp.seats.forEach(seat => {
if (!rows[seat.row.position]) {
rows[seat.row.position] = [];
}
- let state = resp.booked.find(other => other.id == seat.id) ? TheaterSeatState.BOOKED : resp.reserved.find(other => other.id == seat.id) ? TheaterSeatState.RESERVED : TheaterSeatState.AVAILABLE;
+
+ let state = resp.booked.find(other => other.id == seat.id) ? TheaterSeatState.BOOKED
+ : resp.reserved.find(other => other.id == seat.id) ? TheaterSeatState.RESERVED
+ : TheaterSeatState.AVAILABLE;
+
rows[seat.row.position].push({seat: seat, state: state});
+
+ if (seat.row.category && !categoryMap.has(seat.row.category.id)) {
+ categoryMap.set(seat.row.category.id, seat.row.category);
+ }
});
+
+ this.seatCategories = Array.from(categoryMap.values()).sort((a, b) => a.id - b.id);
+
rows = rows.filter(row => row.length > 0).sort((a, b) => a[0].seat.row.position - b[0].seat.row.position);
rows.forEach(row => row.sort((a, b) => a.seat.position - b.seat.position));
return rows;
diff --git a/src/index.html b/src/index.html
index da193c0..2df19e0 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8,6 +8,7 @@
+
From 9c9e9becfb04561b857ec7e3d0355f4fa6268151 Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Thu, 13 Nov 2025 02:10:30 +0100
Subject: [PATCH 4/7] Refactor SelectedSeatsService to use signals
Replaces internal array state with Angular signals for selected seats. Updates methods to use signal API and adds a new method to filter selected seats by category.
---
src/app/selected-seats.service.ts | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/src/app/selected-seats.service.ts b/src/app/selected-seats.service.ts
index 38fdf1d..5a30cc1 100644
--- a/src/app/selected-seats.service.ts
+++ b/src/app/selected-seats.service.ts
@@ -1,30 +1,31 @@
-import {Injectable} from '@angular/core';
+import { Injectable, signal } from '@angular/core';
import {Sitzplatz} from '@infinimotion/model-frontend';
@Injectable({
providedIn: 'root',
})
export class SelectedSeatsService {
- private selectedSeatsList: Sitzplatz[] = [];
+ private selectedSeatsSignal = signal([]);
+
+ get selectedSeats() {
+ return this.selectedSeatsSignal;
+ }
pushSelectedSeat(selectedSeat: Sitzplatz): void {
- this.selectedSeatsList.push(selectedSeat);
- //console.log("Added" + selectedSeat);
- //console.log(this.selectedSeatsList);
+ this.selectedSeatsSignal.update(seats => [...seats, selectedSeat]);
}
removeSelectedSeat(selectedSeat: Sitzplatz): void {
- let removeId = this.selectedSeatsList.indexOf(selectedSeat);
-
- if(removeId !== -1) {
- this.selectedSeatsList.splice(removeId, 1)
- }
- //console.log("Removed" + selectedSeat)
- //console.log(this.selectedSeatsList);
+ this.selectedSeatsSignal.update(seats =>
+ seats.filter(seat => seat.id !== selectedSeat.id)
+ );
}
getSelectedSeatsList(): Sitzplatz[] {
- return this.selectedSeatsList;
+ return this.selectedSeatsSignal();
}
+ getSelectedSeatsByCategory(categoryId: number): Sitzplatz[] {
+ return this.selectedSeatsSignal().filter(seat => seat.row.category.id === categoryId);
+ }
}
From 31108859dae299372d031b6b13e6da4c9c1fe1d3 Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Thu, 13 Nov 2025 02:14:40 +0100
Subject: [PATCH 5/7] Display dynamic seat count and total price per category
Updated seat-selection component to show the actual number of selected seats and calculate the total price per seat category. Integrated SelectedSeatsService and replaced hardcoded values with computed properties for better accuracy and maintainability.
---
src/app/order/order.component.html | 4 +---
.../seat-selection/seat-selection.component.html | 4 ++--
.../seat-selection/seat-selection.component.ts | 15 ++++++++++++---
3 files changed, 15 insertions(+), 8 deletions(-)
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
index 4f31b79..951bfdc 100644
--- a/src/app/order/order.component.html
+++ b/src/app/order/order.component.html
@@ -25,9 +25,7 @@
@for (seatCategory of seatCategories(); track $index) {
-
+
}
@empty {
diff --git a/src/app/seat-selection/seat-selection.component.html b/src/app/seat-selection/seat-selection.component.html
index 853700b..0531616 100644
--- a/src/app/seat-selection/seat-selection.component.html
+++ b/src/app/seat-selection/seat-selection.component.html
@@ -6,6 +6,6 @@
{{ getPriceDisplay(seatCategory().price) }}
- × 2
- 25.00 €
+ × {{selectedSeatsByCategory()}}
+ {{ getPriceDisplay(totalPrice())}}
diff --git a/src/app/seat-selection/seat-selection.component.ts b/src/app/seat-selection/seat-selection.component.ts
index e9f0f8d..07d4307 100644
--- a/src/app/seat-selection/seat-selection.component.ts
+++ b/src/app/seat-selection/seat-selection.component.ts
@@ -1,4 +1,5 @@
-import { Component, input } from '@angular/core';
+import { SelectedSeatsService } from './../selected-seats.service';
+import { Component, computed, inject, input } from '@angular/core';
import { Sitzkategorie } from '@infinimotion/model-frontend';
@Component({
@@ -9,10 +10,18 @@ import { Sitzkategorie } from '@infinimotion/model-frontend';
})
export class SeatSelectionComponent {
seatCategory = input.required();
- amount: number = 1;
+
+ SelectedSeatsService = inject(SelectedSeatsService);
+
+ selectedSeatsByCategory = computed(() =>
+ this.SelectedSeatsService.getSelectedSeatsByCategory(this.seatCategory().id).length
+ );
+
+ totalPrice = computed(() =>
+ this.selectedSeatsByCategory() * this.seatCategory().price
+ );
getPriceDisplay(price: number): string {
return `${(price / 100).toFixed(2)} €`;
}
}
-
From cb065c8e39470ad79eebacc6fc34e87a82f1d36e Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Thu, 13 Nov 2025 09:03:32 +0100
Subject: [PATCH 6/7] Make total ticket price dynamic
Refactored price display logic to use computed total price and formatted output in order and seat selection components. Disabled reservation and booking buttons when no seats are selected. Improved clarity by renaming totalPrice to totalCategoryPrice in seat selection.
---
src/app/order/order.component.html | 6 +++---
src/app/order/order.component.ts | 12 +++++++++++-
src/app/seat-selection/seat-selection.component.html | 4 ++--
src/app/seat-selection/seat-selection.component.ts | 2 +-
4 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
index 951bfdc..4ee64b6 100644
--- a/src/app/order/order.component.html
+++ b/src/app/order/order.component.html
@@ -41,13 +41,13 @@
Tickets gesamt:
- 75,00 €
+ {{ getPriceDisplay(totalPrice()) }}
-
-
+
+
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts
index 3f3b094..5e66746 100644
--- a/src/app/order/order.component.ts
+++ b/src/app/order/order.component.ts
@@ -1,6 +1,7 @@
+import { SelectedSeatsService } from './../selected-seats.service';
import { LoadingService } from './../loading.service';
import { Sitzkategorie, Vorstellung } from '@infinimotion/model-frontend';
-import { Component, inject, input } from '@angular/core';
+import { Component, computed, inject, input } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
@Component({
@@ -15,6 +16,7 @@ export class OrderComponent {
private _formBuilder = inject(FormBuilder);
loadingService = inject(LoadingService);
+ private selectedSeatsService = inject(SelectedSeatsService);
firstFormGroup = this._formBuilder.group({
firstCtrl: ['', Validators.required],
@@ -22,4 +24,12 @@ export class OrderComponent {
secondFormGroup = this._formBuilder.group({
secondCtrl: ['', Validators.required],
});
+
+ totalPrice = computed(() =>
+ this.selectedSeatsService.getSelectedSeatsList().reduce((sum, seat) => sum + seat.row.category.price, 0)
+ );
+
+ getPriceDisplay(price: number): string {
+ return `${(price / 100).toFixed(2)} €`;
+ }
}
diff --git a/src/app/seat-selection/seat-selection.component.html b/src/app/seat-selection/seat-selection.component.html
index 0531616..53562eb 100644
--- a/src/app/seat-selection/seat-selection.component.html
+++ b/src/app/seat-selection/seat-selection.component.html
@@ -6,6 +6,6 @@
{{ getPriceDisplay(seatCategory().price) }}
- × {{selectedSeatsByCategory()}}
- {{ getPriceDisplay(totalPrice())}}
+ × {{ selectedSeatsByCategory() }}
+ {{ getPriceDisplay(totalCategoryPrice()) }}
diff --git a/src/app/seat-selection/seat-selection.component.ts b/src/app/seat-selection/seat-selection.component.ts
index 07d4307..9c5ca14 100644
--- a/src/app/seat-selection/seat-selection.component.ts
+++ b/src/app/seat-selection/seat-selection.component.ts
@@ -17,7 +17,7 @@ export class SeatSelectionComponent {
this.SelectedSeatsService.getSelectedSeatsByCategory(this.seatCategory().id).length
);
- totalPrice = computed(() =>
+ totalCategoryPrice = computed(() =>
this.selectedSeatsByCategory() * this.seatCategory().price
);
From 41c9d85e9bd798062cd4a279e8791081e61f656b Mon Sep 17 00:00:00 2001
From: Piet Ostendorp
Date: Thu, 13 Nov 2025 22:36:09 +0100
Subject: [PATCH 7/7] Add payment form and improve order stepper UI
Introduces a payment step with card input masking using ngx-mask, refactors the order stepper to include address and payment forms with validation, and enhances UI/UX with new styles and layout adjustments. Also updates dependencies and module imports to support ngx-mask and Material Checkbox.
---
package-lock.json | 43 ++++-
package.json | 1 +
src/app/app-module.ts | 16 +-
src/app/order/order.component.css | 12 ++
src/app/order/order.component.html | 163 +++++++++++++-----
src/app/order/order.component.ts | 53 +++++-
.../performance-info.component.html | 2 +-
.../performance-info.component.ts | 2 +-
src/custom-theme.scss | 4 +-
9 files changed, 231 insertions(+), 65 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 98caa25..0000198 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"@angular/router": "^20.3.0",
"@infinimotion/model-frontend": "^0.0.89",
"@tailwindcss/postcss": "^4.1.14",
+ "ngx-mask": "^20.0.3",
"postcss": "^8.5.6",
"rxjs": "~7.8.0",
"tailwindcss": "^4.1.14",
@@ -440,6 +441,7 @@
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.12.tgz",
"integrity": "sha512-hz8GtiMy3N9/e8407ZfrByHD5GEC4SkWtxyUknWuTM9P88AOie0jDZ6CfQg9gQ0OJX+6BAbJV3RpYZA1uzNUqA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"parse5": "^8.0.0",
"tslib": "^2.3.0"
@@ -490,6 +492,7 @@
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.10.tgz",
"integrity": "sha512-12fEzvKbEqjqy1fSk9DMYlJz6dF1MJVXuC5BB+oWWJpd+2lfh4xJ62pkvvLGAICI89hfM5n9Cy5kWnXwnqPZsA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -506,6 +509,7 @@
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.10.tgz",
"integrity": "sha512-cW939Lr8GZjPSYfbQKIDNrUaHWmn2M+zBbERThfq5skLuY+xM60bJFv4NqBekfX6YqKLCY62ilUZlnImYIXaqA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -519,6 +523,7 @@
"integrity": "sha512-9BemvpFxA26yIVdu8ROffadMkEdlk/AQQ2Jb486w7RPkrvUQ0pbEJukhv9aryJvhbMopT66S5H/j4ipOUMzmzQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/core": "7.28.3",
"@jridgewell/sourcemap-codec": "^1.4.14",
@@ -551,6 +556,7 @@
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.10.tgz",
"integrity": "sha512-g99Qe+NOVo72OLxowVF9NjCckswWYHmvO7MgeiZTDJbTjF9tXH96dMx7AWq76/GUinV10sNzDysVW16NoAbCRQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -576,6 +582,7 @@
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.10.tgz",
"integrity": "sha512-9yWr51EUauTEINB745AaHwZNTHLpXIm4uxuykxzOg+g2QskEgVfH26uS8G2ogdNuwYpB8wnsXWr34qhM3qgOWw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -611,6 +618,7 @@
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.10.tgz",
"integrity": "sha512-UV8CGoB5P3FmJciI3/I/n3L7C3NVgGh7bIlZ1BaB/qJDtv0Wq0rRAGwmT/Z3gwmrRtfHZWme7/CeQ2CYJmMyUQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
@@ -677,6 +685,7 @@
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -1633,6 +1642,7 @@
"integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@inquirer/checkbox": "^4.2.1",
"@inquirer/confirm": "^5.1.14",
@@ -3865,6 +3875,7 @@
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -4198,6 +4209,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -5294,6 +5306,7 @@
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.0",
@@ -6245,7 +6258,8 @@
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.9.0.tgz",
"integrity": "sha512-OMUvF1iI6+gSRYOhMrH4QYothVLN9C3EJ6wm4g7zLJlnaTl8zbaPOr0bTw70l7QxkoM7sVFOWo83u9B2Fe2Zng==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/jiti": {
"version": "2.6.1",
@@ -6339,6 +6353,7 @@
"integrity": "sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@colors/colors": "1.5.0",
"body-parser": "^1.19.0",
@@ -7055,6 +7070,7 @@
"integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
@@ -7693,6 +7709,20 @@
"node": ">= 0.6"
}
},
+ "node_modules/ngx-mask": {
+ "version": "20.0.3",
+ "resolved": "https://registry.npmjs.org/ngx-mask/-/ngx-mask-20.0.3.tgz",
+ "integrity": "sha512-5bmrgbFGudj0mFN6cPv/TI+cFJxT4l61mLIFskdvaXsJL/Oj7thRmWYqvqHXjCboOcx8gT6T/Zypl5u9l2J8Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": ">=14.0.0",
+ "@angular/core": ">=14.0.0",
+ "@angular/forms": ">=14.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",
@@ -8712,6 +8742,7 @@
"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"
}
@@ -8768,6 +8799,7 @@
"integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"chokidar": "^4.0.0",
"immutable": "^5.0.2",
@@ -9616,7 +9648,8 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
+ "license": "0BSD",
+ "peer": true
},
"node_modules/tuf-js": {
"version": "3.1.0",
@@ -9654,6 +9687,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -9840,6 +9874,7 @@
"integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -10243,6 +10278,7 @@
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
@@ -10261,7 +10297,8 @@
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz",
"integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
}
}
}
diff --git a/package.json b/package.json
index a8410ad..0a846b3 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"@angular/router": "^20.3.0",
"@infinimotion/model-frontend": "^0.0.89",
"@tailwindcss/postcss": "^4.1.14",
+ "ngx-mask": "^20.0.3",
"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 7bdff2c..f692e72 100644
--- a/src/app/app-module.ts
+++ b/src/app/app-module.ts
@@ -3,11 +3,11 @@ import { NgModule, provideBrowserGlobalErrorListeners } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { provideHttpClient, withFetch } from '@angular/common/http';
-
-
import { AppRoutingModule } from './app-routing-module';
import { App } from './app';
+import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask';
+
import { MatIconModule } from '@angular/material/icon';
import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar';
@@ -21,6 +21,7 @@ import { MatButtonModule, MatIconButton } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatDialogClose, MatDialogTitle, MatDialogContent, MatDialogActions } from "@angular/material/dialog";
import { MatStepperModule } from '@angular/material/stepper';
+import { MatCheckboxModule } from '@angular/material/checkbox';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
@@ -29,7 +30,6 @@ import { MainLayoutComponent } from './layouts/main-layout/main-layout.component
import { NavbarComponent } from './navbar/navbar.component';
import { PocModelComponent } from './poc-model-component/poc-model-component';
import { ScheduleComponent } from './schedule/schedule.component';
-
import { MovieDurationComponent } from './movie-duration/movie-duration.component';
import { MoviePerformanceComponent } from './movie-performance/movie-performance.component';
import { MoviePosterComponent } from './movie-poster/movie-poster.component';
@@ -115,13 +115,17 @@ import { NoSeatsInHallComponent } from './no-seats-in-hall/no-seats-in-hall.comp
MatDialogTitle,
MatDialogContent,
MatDialogActions,
- MatStepperModule
-],
+ MatCheckboxModule,
+ MatStepperModule,
+ NgxMaskDirective,
+ NgxMaskPipe,
+ ],
providers: [
provideBrowserGlobalErrorListeners(),
provideHttpClient(
withFetch(),
- )
+ ),
+ provideNgxMask(),
],
bootstrap: [App]
})
diff --git a/src/app/order/order.component.css b/src/app/order/order.component.css
index b19ef5b..367c809 100644
--- a/src/app/order/order.component.css
+++ b/src/app/order/order.component.css
@@ -1,7 +1,19 @@
mat-stepper {
background: transparent !important;
}
+::ng-deep .mat-step-header {
+ background-color: transparent !important;
+}
+
::ng-deep .mat-horizontal-stepper-header{
pointer-events: none !important;
}
+
+.performance-info-space {
+ margin-top: calc(var(--spacing) * 24)
+}
+
+::ng-deep .checkbox-invalid.mat-mdc-checkbox .mat-internal-form-field {
+ color: red !important;
+}
diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html
index 4ee64b6..dce14e7 100644
--- a/src/app/order/order.component.html
+++ b/src/app/order/order.component.html
@@ -1,4 +1,4 @@
-
+
@if (loadingService.loading$ | async){
@@ -10,19 +10,21 @@
}
@else if (performance()) {
-
-
-