From 50cac8ac24793a09e4dc4bf541c23b974bd2bbc0 Mon Sep 17 00:00:00 2001 From: Piet Ostendorp Date: Fri, 14 Nov 2025 17:56:33 +0100 Subject: [PATCH] Add reservation and purchase functionality Introduces ReservationSuccess, ReservationFailed, PurchaseSuccess, PurchaseFailed, TicketSmall, and TicketList components for handling and displaying reservation and purchase outcomes. Updates order flow logic in OrderComponent to support reservation and purchase states, disables/enables form inputs during submission, and integrates new UI feedback. Also adds angularx-qrcode dependency and updates @infinimotion/model-frontend version. --- package-lock.json | 273 +++++++++++++++++- package.json | 3 +- src/app/app-module.ts | 14 + .../movie-import-search-info.component.html | 2 +- src/app/order/order.component.html | 188 +++++++----- src/app/order/order.component.ts | 170 ++++++++++- .../purchase-failed.component.css | 0 .../purchase-failed.component.html | 11 + .../purchase-failed.component.ts | 11 + .../purchase-success.component.css | 0 .../purchase-success.component.html | 11 + .../purchase-success.component.ts | 12 + .../reservation-failed.component.css | 0 .../reservation-failed.component.html | 11 + .../reservation-failed.component.ts | 11 + .../reservation-success.component.css | 0 .../reservation-success.component.html | 17 ++ .../reservation-success.component.ts | 16 + src/app/seat/seat.component.css | 15 + src/app/seat/seat.component.html | 9 +- src/app/seat/seat.component.ts | 16 +- src/app/selected-seats.service.ts | 2 +- .../theater-overlay.component.ts | 1 + src/app/ticket-list/ticket-list.component.css | 41 +++ .../ticket-list/ticket-list.component.html | 9 + src/app/ticket-list/ticket-list.component.ts | 12 + .../ticket-small/ticket-small.component.css | 0 .../ticket-small/ticket-small.component.html | 16 + .../ticket-small/ticket-small.component.ts | 16 + src/custom-theme.scss | 41 +++ 30 files changed, 821 insertions(+), 107 deletions(-) create mode 100644 src/app/purchase-failed/purchase-failed.component.css create mode 100644 src/app/purchase-failed/purchase-failed.component.html create mode 100644 src/app/purchase-failed/purchase-failed.component.ts create mode 100644 src/app/purchase-success/purchase-success.component.css create mode 100644 src/app/purchase-success/purchase-success.component.html create mode 100644 src/app/purchase-success/purchase-success.component.ts create mode 100644 src/app/reservation-failed/reservation-failed.component.css create mode 100644 src/app/reservation-failed/reservation-failed.component.html create mode 100644 src/app/reservation-failed/reservation-failed.component.ts create mode 100644 src/app/reservation-success/reservation-success.component.css create mode 100644 src/app/reservation-success/reservation-success.component.html create mode 100644 src/app/reservation-success/reservation-success.component.ts create mode 100644 src/app/ticket-list/ticket-list.component.css create mode 100644 src/app/ticket-list/ticket-list.component.html create mode 100644 src/app/ticket-list/ticket-list.component.ts create mode 100644 src/app/ticket-small/ticket-small.component.css create mode 100644 src/app/ticket-small/ticket-small.component.html create mode 100644 src/app/ticket-small/ticket-small.component.ts diff --git a/package-lock.json b/package-lock.json index 0000198..9dbe2fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,9 @@ "@angular/material": "^20.2.9", "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", - "@infinimotion/model-frontend": "^0.0.89", + "@infinimotion/model-frontend": "^0.0.102", "@tailwindcss/postcss": "^4.1.14", + "angularx-qrcode": "^20.0.0", "ngx-mask": "^20.0.3", "postcss": "^8.5.6", "rxjs": "~7.8.0", @@ -1401,9 +1402,9 @@ } }, "node_modules/@infinimotion/model-frontend": { - "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==", + "version": "0.0.102", + "resolved": "https://git.infinimotion.de/api/packages/infinimotion/npm/%40infinimotion%2Fmodel-frontend/-/0.0.102/model-frontend-0.0.102.tgz", + "integrity": "sha512-NJV9bSBubdOZ1GBIe9To3o/hh6AZscJcTyaZY2nGmMxH+GhtvO1AHmjhrQeRjwAKFiwZMEwEg4ktFiOAp3MTMQ==", "license": "ISC" }, "node_modules/@inquirer/ansi": { @@ -3995,6 +3996,19 @@ "node": ">= 14.0.0" } }, + "node_modules/angularx-qrcode": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/angularx-qrcode/-/angularx-qrcode-20.0.0.tgz", + "integrity": "sha512-WZolRZztQsQxOXqodNSDicxPWNO79t/AT4wts+DxwYdtdXb1RELfZjtax9oGMQQ6mEZ6bwk5GqBGEDB3Y+cSqw==", + "license": "MIT", + "dependencies": { + "qrcode": "1.5.4", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^20.0.0" + } + }, "node_modules/ansi-escapes": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", @@ -4387,6 +4401,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001754", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", @@ -4547,7 +4570,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -4560,7 +4582,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -4806,6 +4827,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4843,6 +4873,12 @@ "dev": true, "license": "MIT" }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -5447,6 +5483,19 @@ "node": ">= 0.8" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -5586,7 +5635,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -7136,6 +7184,18 @@ "@lmdb/lmdb-win32-x64": "3.4.2" } }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -8159,6 +8219,33 @@ "license": "MIT", "optional": true }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-map": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", @@ -8172,6 +8259,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -8322,6 +8418,15 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -8426,6 +8531,15 @@ "node": ">=16.20.0" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -8516,6 +8630,125 @@ "node": ">=0.9" } }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/qrcode/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", @@ -8583,7 +8816,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8599,6 +8831,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -8867,6 +9105,12 @@ "node": ">= 18" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10009,11 +10253,16 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -10118,7 +10367,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10128,7 +10376,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -10144,14 +10391,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10161,7 +10406,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10176,7 +10420,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" diff --git a/package.json b/package.json index 0a846b3..41b72cb 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,9 @@ "@angular/material": "^20.2.9", "@angular/platform-browser": "^20.3.0", "@angular/router": "^20.3.0", - "@infinimotion/model-frontend": "^0.0.89", + "@infinimotion/model-frontend": "^0.0.102", "@tailwindcss/postcss": "^4.1.14", + "angularx-qrcode": "^20.0.0", "ngx-mask": "^20.0.3", "postcss": "^8.5.6", "rxjs": "~7.8.0", diff --git a/src/app/app-module.ts b/src/app/app-module.ts index f692e72..2b87502 100644 --- a/src/app/app-module.ts +++ b/src/app/app-module.ts @@ -7,6 +7,7 @@ import { AppRoutingModule } from './app-routing-module'; import { App } from './app'; import { NgxMaskDirective, NgxMaskPipe, provideNgxMask } from 'ngx-mask'; +import { QRCodeComponent } from 'angularx-qrcode'; import { MatIconModule } from '@angular/material/icon'; import { MatTabsModule } from '@angular/material/tabs'; @@ -55,6 +56,12 @@ 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'; +import { ReservationSuccessComponent } from './reservation-success/reservation-success.component'; +import { ReservationFailedComponent } from './reservation-failed/reservation-failed.component'; +import { PurchaseSuccessComponent } from './purchase-success/purchase-success.component'; +import { PurchaseFailedComponent } from './purchase-failed/purchase-failed.component'; +import { TicketSmallComponent } from './ticket-small/ticket-small.component'; +import { TicketListComponent } from './ticket-list/ticket-list.component'; @NgModule({ @@ -92,6 +99,12 @@ import { NoSeatsInHallComponent } from './no-seats-in-hall/no-seats-in-hall.comp OrderComponent, SeatSelectionComponent, NoSeatsInHallComponent, + ReservationSuccessComponent, + ReservationFailedComponent, + PurchaseSuccessComponent, + PurchaseFailedComponent, + TicketSmallComponent, + TicketListComponent, ], imports: [ AppRoutingModule, @@ -119,6 +132,7 @@ import { NoSeatsInHallComponent } from './no-seats-in-hall/no-seats-in-hall.comp MatStepperModule, NgxMaskDirective, NgxMaskPipe, + QRCodeComponent, ], providers: [ provideBrowserGlobalErrorListeners(), diff --git a/src/app/movie-import-search-info/movie-import-search-info.component.html b/src/app/movie-import-search-info/movie-import-search-info.component.html index fa3bab7..0fe7563 100644 --- a/src/app/movie-import-search-info/movie-import-search-info.component.html +++ b/src/app/movie-import-search-info/movie-import-search-info.component.html @@ -13,7 +13,7 @@ Erscheinungsjahr: {{ movie().year }} - diff --git a/src/app/order/order.component.html b/src/app/order/order.component.html index dce14e7..2b5831e 100644 --- a/src/app/order/order.component.html +++ b/src/app/order/order.component.html @@ -1,6 +1,6 @@
-@if (loadingService.loading$ | async){ +@if (!performance() && (loadingService.loading$ | async)){
+ @if(isSubmitting) { +
+ +
+ } + Warenkorb @@ -48,8 +58,8 @@
- - + +
@@ -59,32 +69,45 @@
- - - Name - - @if (fData['name'].hasError('minlength')) { Mindestens 3 Zeichen } - + @if (seatsReserved && !isSubmitting) { +
+ @if (successful) { + + } @else { + + } + } + @else { - - - E-Mail Adresse - - @if (fData['email'].hasError('email')) { Ungültige E-Mail-Adresse } - + + + Name + + @if (fData['name'].hasError('minlength')) { Mindestens 3 Zeichen } + - -
- - Ich akzeptiere die AGB und die Datenbestimmung - -
+ + + E-Mail Adresse + + @if (fData['email'].hasError('email')) { Ungültige E-Mail-Adresse } + + + +
+ + Ich akzeptiere die AGB und die Datenbestimmung + +
+ + +
+ + +
+ + } - -
- - -
@@ -93,66 +116,77 @@
- - - Kartennummer - - @if (fPayment['cardNumber'].hasError('pattern')) { Ungültige Kartennummer } + @if (seatsPurchased && !isSubmitting) { +
+ @if (successful) { + + } @else { + + } + } + @else { -
- - - - Kartenname - - @if (fPayment['cardName'].hasError('minlength')) { Mindestens 3 Zeichen } - - - -
- - Gültig bis (MM/YY) + + + Kartennummer - @if (fPayment['expiry'].hasError('pattern')) { Ungültiges Format } + @if (fPayment['cardNumber'].hasError('pattern')) { Ungültige Kartennummer } + - - CVV - - @if (fPayment['cvv'].hasError('pattern')) { 3–4 Ziffernt } + + + Kartenname + + @if (fPayment['cardName'].hasError('minlength')) { Mindestens 3 Zeichen } -
- -
- - encrypted - -

- Ihre Zahlung wird sicher über unsere Partner verarbeitet.
Wir speichern keine Zahlungsinformationen. -

-
+ +
+ + Gültig bis (MM/YY) + + @if (fPayment['expiry'].hasError('pattern')) { Ungültiges Format } + - -
- - -
+ + CVV + + @if (fPayment['cvv'].hasError('pattern')) { 3–4 Ziffernt } + +
+ + +
+ + encrypted + +

+ Ihre Zahlung wird sicher über unsere Partner verarbeitet.
Wir speichern keine Zahlungsinformationen. +

+
+ + +
+ + +
+ }
diff --git a/src/app/order/order.component.ts b/src/app/order/order.component.ts index f1c0989..1449912 100644 --- a/src/app/order/order.component.ts +++ b/src/app/order/order.component.ts @@ -1,9 +1,12 @@ import { SelectedSeatsService } from './../selected-seats.service'; import { LoadingService } from './../loading.service'; -import { Sitzkategorie, Vorstellung } from '@infinimotion/model-frontend'; +import { Bestellung, Eintrittskarte, Sitzkategorie, Sitzplatz, Vorstellung } from '@infinimotion/model-frontend'; import { Component, computed, inject, input } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; +import { HttpService } from '../http.service'; +import { catchError, tap, finalize } from 'rxjs'; +import { MatStepper } from '@angular/material/stepper'; @Component({ selector: 'app-order', @@ -17,8 +20,17 @@ export class OrderComponent { submitted = false; + performance = input(); + seatCategories = input.required(); + + loadingService = inject(LoadingService); + private httpService = inject(HttpService) + private selectedSeatsService = inject(SelectedSeatsService); + constructor(private fb: FormBuilder) {} + // Form-Validation + ngOnInit(): void { this.paymentForm = this.fb.group({ cardNumber: ['', [Validators.required, Validators.pattern(/^\d{16}$/)]], @@ -37,23 +49,31 @@ export class OrderComponent { get fPayment() { return this.paymentForm.controls; } onSubmit() { + if (this.dataForm.invalid) return; if (this.paymentForm.invalid) return; - console.log('Zahlungsdaten:', this.paymentForm.value); } onStepChange(event: StepperSelectionEvent) { this.submitted = false; + + if(event.selectedIndex != 0) { + this.selectedSeatsService.setSeatIsSelectableFalse() + } else { + this.selectedSeatsService.setSeatIsSelectableTrue() + } } - stupidCheckboxWorkaround() { + nextPhaseButtonClicked(stepper: MatStepper) { this.submitted = true; + if (this.dataForm.invalid) return; + + if (this.submissionMode === "reservation") { + this.makeReservation(); + } else if (this.submissionMode === "purchase") { + stepper.next(); + } } - performance = input(); - seatCategories = input.required(); - - loadingService = inject(LoadingService); - private selectedSeatsService = inject(SelectedSeatsService); totalPrice = computed(() => this.selectedSeatsService.getSelectedSeatsList().reduce((sum, seat) => sum + seat.row.category.price, 0) @@ -67,4 +87,138 @@ export class OrderComponent { return `${(price / 100).toFixed(2)} €`; } + isSubmitting: boolean = false; + secondPhaseButtonText: string = "Loading..." + submissionMode!: 'reservation' | 'purchase'; + + seatsReserved: boolean = false; + seatsPurchased: boolean = false; + successful: boolean = false; + + reservationClicked() { + this.submissionMode = "reservation"; + if (this.totalSeats() > 1) { + this.secondPhaseButtonText = "Sitzplätze reservieren" + } else { + this.secondPhaseButtonText = "Sitzplatz reservieren" + } + } + + purchaseClicked() { + this.submissionMode = "purchase" + this.secondPhaseButtonText = "Weiter zur Zahlung" + } + + makeReservation() { + this.loadingService.show(); + this.disableInputs() + + const order = this.generateNewOrderObject(this.dataForm.value.email, false); + const seats = this.selectedSeatsService.getSelectedSeatsList(); + const performance = this.performance()!; + this.successful = true; + this.sendToBackend(order, seats, performance); + this.seatsReserved = true; + } + + makePurchase() { + this.loadingService.show(); + this.disableInputs() + + const order = this.generateNewOrderObject(this.dataForm.value.email, true); + const seats = this.selectedSeatsService.getSelectedSeatsList(); + const performance = this.performance()!; + this.successful = true; + this.sendToBackend(order, seats, performance); + this.seatsPurchased = true; + } + + createdOrder!: Bestellung; + createdTickets!: Eintrittskarte[]; + + sendToBackend(order: Bestellung, seats: Sitzplatz[], performance: Vorstellung) { + this.httpService.addOrder(order).pipe( + tap(createdOrder => { + this.createdOrder = createdOrder; + + const ticketCreations = seats.map(seat => { + const ticket = this.generateNewTicketObject(performance, seat, createdOrder); + return this.httpService.addTicket(ticket); + }); + + Promise.all(ticketCreations.map(obs => obs.toPromise())) + .then(createdTickets => { + this.createdTickets = createdTickets.filter( + (ticket): ticket is Eintrittskarte => ticket !== undefined + ); + + this.loadingService.hide(); + this.enableInputs(); + }) + .catch(err => { + this.loadingService.showError(err); + this.successful = false; + console.error('Fehler beim Anlegen der Eintrittskarten', err); + }); + }), + catchError(err => { + this.loadingService.showError(err); + this.successful = false; + console.error('Fehler beim Anlegen der Bestellung', err); + return []; + }), + finalize(() => { + this.enableInputs(); + }) + ).subscribe(); + } + + + + + private generateCode(length: number = 6): string { + const chars = "ABCDEFGHJKLMNPQRSUVWXYZ23456789"; + let result = ""; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * chars.length); + result += chars[randomIndex]; + } + + return result; + } + + private generateNewOrderObject(mail: string, isBooked: boolean): Bestellung { + return{ + id: 0, // Wird durch Backend gesetzt + mail: mail, + code: this.generateCode(length=6), + reserved: new Date(), + booked: isBooked ? new Date() : null, + cancelled: null, + }; + } + + private generateNewTicketObject(show: Vorstellung, seat: Sitzplatz, order: Bestellung): Eintrittskarte { + return { + id: 0, // Wird durch Backend gesetzt + code: 'T' + this.generateCode(length=7), + show: show, + seat: seat, + order: order + }; + } + + private disableInputs() { + this.dataForm.disable(); + this.paymentForm.disable(); + this.isSubmitting = true; + } + + private enableInputs() { + this.dataForm.enable(); + this.paymentForm.enable(); + this.isSubmitting = false; + } + } diff --git a/src/app/purchase-failed/purchase-failed.component.css b/src/app/purchase-failed/purchase-failed.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/purchase-failed/purchase-failed.component.html b/src/app/purchase-failed/purchase-failed.component.html new file mode 100644 index 0000000..564513a --- /dev/null +++ b/src/app/purchase-failed/purchase-failed.component.html @@ -0,0 +1,11 @@ +
+ + warning + +

Kauf fehlgeschlagen!

+

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

+ + + +
+ diff --git a/src/app/purchase-failed/purchase-failed.component.ts b/src/app/purchase-failed/purchase-failed.component.ts new file mode 100644 index 0000000..619d3e9 --- /dev/null +++ b/src/app/purchase-failed/purchase-failed.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-purchase-failed', + standalone: false, + templateUrl: './purchase-failed.component.html', + styleUrl: './purchase-failed.component.css', +}) +export class PurchaseFailedComponent { + +} diff --git a/src/app/purchase-success/purchase-success.component.css b/src/app/purchase-success/purchase-success.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/purchase-success/purchase-success.component.html b/src/app/purchase-success/purchase-success.component.html new file mode 100644 index 0000000..4d40726 --- /dev/null +++ b/src/app/purchase-success/purchase-success.component.html @@ -0,0 +1,11 @@ +
+

Vielen Dank für Ihren Einkauf!

+

Ihre Sitzplätze wurden erfolgreich gebucht.

+ + + + + + +
+ diff --git a/src/app/purchase-success/purchase-success.component.ts b/src/app/purchase-success/purchase-success.component.ts new file mode 100644 index 0000000..8dad591 --- /dev/null +++ b/src/app/purchase-success/purchase-success.component.ts @@ -0,0 +1,12 @@ +import { Eintrittskarte } from '@infinimotion/model-frontend'; +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-purchase-success', + standalone: false, + templateUrl: './purchase-success.component.html', + styleUrl: './purchase-success.component.css', +}) +export class PurchaseSuccessComponent { + tickets = input.required(); +} diff --git a/src/app/reservation-failed/reservation-failed.component.css b/src/app/reservation-failed/reservation-failed.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/reservation-failed/reservation-failed.component.html b/src/app/reservation-failed/reservation-failed.component.html new file mode 100644 index 0000000..792a731 --- /dev/null +++ b/src/app/reservation-failed/reservation-failed.component.html @@ -0,0 +1,11 @@ +
+ + warning + +

Reservierung fehlgeschlagen!

+

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-failed/reservation-failed.component.ts b/src/app/reservation-failed/reservation-failed.component.ts new file mode 100644 index 0000000..7693d1e --- /dev/null +++ b/src/app/reservation-failed/reservation-failed.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-reservation-failed', + standalone: false, + templateUrl: './reservation-failed.component.html', + styleUrl: './reservation-failed.component.css', +}) +export class ReservationFailedComponent { + +} diff --git a/src/app/reservation-success/reservation-success.component.css b/src/app/reservation-success/reservation-success.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/reservation-success/reservation-success.component.html b/src/app/reservation-success/reservation-success.component.html new file mode 100644 index 0000000..0a463a0 --- /dev/null +++ b/src/app/reservation-success/reservation-success.component.html @@ -0,0 +1,17 @@ +
+

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/reservation-success/reservation-success.component.ts b/src/app/reservation-success/reservation-success.component.ts new file mode 100644 index 0000000..8754ae6 --- /dev/null +++ b/src/app/reservation-success/reservation-success.component.ts @@ -0,0 +1,16 @@ +import { Bestellung } from '@infinimotion/model-frontend'; +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-reservation-success', + standalone: false, + templateUrl: './reservation-success.component.html', + styleUrl: './reservation-success.component.css', +}) +export class ReservationSuccessComponent { + order = input.required(); + + cancelReservation() { + // Logic to cancel the reservation + } +} diff --git a/src/app/seat/seat.component.css b/src/app/seat/seat.component.css index e69de29..964cf92 100644 --- a/src/app/seat/seat.component.css +++ b/src/app/seat/seat.component.css @@ -0,0 +1,15 @@ +@keyframes blink { + 0% { + color: #6366f1; + } + 50% { + color: #ffde05; + } + 100% { + color: #6366f1; + } +} + +.blink { + animation: blink 1s ease-in-out infinite; +} diff --git a/src/app/seat/seat.component.html b/src/app/seat/seat.component.html index f115ccc..57ca2a1 100644 --- a/src/app/seat/seat.component.html +++ b/src/app/seat/seat.component.html @@ -1,6 +1,11 @@ - diff --git a/src/app/seat/seat.component.ts b/src/app/seat/seat.component.ts index e029b90..06b3e18 100644 --- a/src/app/seat/seat.component.ts +++ b/src/app/seat/seat.component.ts @@ -19,17 +19,29 @@ export class SeatComponent{ protected readonly TheaterSeatState = TheaterSeatState; getSeatStateColor(): string { + if (!this.seatService.getSeatIsSelectable()) return 'gray' switch (this.state()) { case TheaterSeatState.RESERVED: - return 'orange'; + return '#d6c9a9'; case TheaterSeatState.BOOKED: - return 'red'; + return '#d9abab'; default: case TheaterSeatState.AVAILABLE: return 'black'; } } + isHoverable(): boolean { + switch (this.state()) { + default: + case TheaterSeatState.AVAILABLE: + return this.seatService.getSeatIsSelectable(); + case TheaterSeatState.RESERVED: + case TheaterSeatState.BOOKED: + return false; + } + } + updateSelectedSeats(selectedSeat: Sitzplatz) : void { if(!this.selected){ this.seatService.pushSelectedSeat(selectedSeat); diff --git a/src/app/selected-seats.service.ts b/src/app/selected-seats.service.ts index 329ea0b..5faed31 100644 --- a/src/app/selected-seats.service.ts +++ b/src/app/selected-seats.service.ts @@ -34,7 +34,7 @@ export class SelectedSeatsService { this.selectedSeatsSignal.set([]); } - getSeatIsSelected(): boolean{ + getSeatIsSelectable(): boolean{ return this.seatIsSelectable; } diff --git a/src/app/theater-overlay/theater-overlay.component.ts b/src/app/theater-overlay/theater-overlay.component.ts index 1fd7c20..dce8dcc 100644 --- a/src/app/theater-overlay/theater-overlay.component.ts +++ b/src/app/theater-overlay/theater-overlay.component.ts @@ -27,6 +27,7 @@ export class TheaterOverlayComponent implements OnInit { ngOnInit() { this.showId = Number(this.route.snapshot.paramMap.get('id')!); this.selectedSeatService.clearSelectedSeatsList(); + this.selectedSeatService.setSeatIsSelectableTrue(); this.loadPerformanceAndSeats(); } diff --git a/src/app/ticket-list/ticket-list.component.css b/src/app/ticket-list/ticket-list.component.css new file mode 100644 index 0000000..c86a2b9 --- /dev/null +++ b/src/app/ticket-list/ticket-list.component.css @@ -0,0 +1,41 @@ +.ticket-container { + max-height: 200px; + overflow-y: auto; + padding: 8px; + position: relative; +} + +.ticket-container::before, +.ticket-container::after { + content: ''; + position: sticky; + left: 0; + right: 0; + height: 20px; + pointer-events: none; + z-index: 2; +} + +.ticket-container::before { + top: 0; + background: linear-gradient(to bottom, rgba(0,0,0,0.1), transparent); +} + +.ticket-container::after { + bottom: 0; + background: linear-gradient(to top, rgba(0,0,0,0.1), transparent); +} + + +.ticket-container::-webkit-scrollbar { + width: 8px; +} + +.ticket-container::-webkit-scrollbar-track { + background: transparent; +} + +.ticket-container::-webkit-scrollbar-thumb { + background-color: rgba(0,0,0,0.2); + border-radius: 4px; +} diff --git a/src/app/ticket-list/ticket-list.component.html b/src/app/ticket-list/ticket-list.component.html new file mode 100644 index 0000000..a03fe3e --- /dev/null +++ b/src/app/ticket-list/ticket-list.component.html @@ -0,0 +1,9 @@ +
+ @for (ticket of tickets(); track $index) { + + @if ($index + 1 != tickets().length) { +
+ } + } +
+ diff --git a/src/app/ticket-list/ticket-list.component.ts b/src/app/ticket-list/ticket-list.component.ts new file mode 100644 index 0000000..434b9ab --- /dev/null +++ b/src/app/ticket-list/ticket-list.component.ts @@ -0,0 +1,12 @@ +import { Component, input } from '@angular/core'; +import { Eintrittskarte } from '@infinimotion/model-frontend'; + +@Component({ + selector: 'app-ticket-list', + standalone: false, + templateUrl: './ticket-list.component.html', + styleUrl: './ticket-list.component.css', +}) +export class TicketListComponent { + tickets = input.required(); +} diff --git a/src/app/ticket-small/ticket-small.component.css b/src/app/ticket-small/ticket-small.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/ticket-small/ticket-small.component.html b/src/app/ticket-small/ticket-small.component.html new file mode 100644 index 0000000..a0db66d --- /dev/null +++ b/src/app/ticket-small/ticket-small.component.html @@ -0,0 +1,16 @@ +
+ + local_activity + +
+

{{ticket().seat.row.category.name}} • Reihe {{convertIntoRowName(ticket().seat.row.position)}} Platz {{ticket().seat.position}}

+
+

Ticketcode:

+

+ {{ ticket().code }} +

+
+
+ +
+ diff --git a/src/app/ticket-small/ticket-small.component.ts b/src/app/ticket-small/ticket-small.component.ts new file mode 100644 index 0000000..30820c2 --- /dev/null +++ b/src/app/ticket-small/ticket-small.component.ts @@ -0,0 +1,16 @@ +import { Eintrittskarte } from '@infinimotion/model-frontend'; +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-ticket-small', + standalone: false, + templateUrl: './ticket-small.component.html', + styleUrl: './ticket-small.component.css', +}) +export class TicketSmallComponent { + ticket = input.required(); + + convertIntoRowName(n: number): string { + return String.fromCharCode(64 + n); + } +} diff --git a/src/custom-theme.scss b/src/custom-theme.scss index 640a19c..6eddae9 100644 --- a/src/custom-theme.scss +++ b/src/custom-theme.scss @@ -66,6 +66,47 @@ html.dark { color: white; } + + +.error-button { + @include mat.button-overrides(( + outlined-ripple-color: rgba(255, 0, 0, 0.1), + )); + + &.mdc-button:not(.mat-mdc-outlined-button):not(:disabled) { + background-color: var(--color-red-500) !important; + } + + &.mdc-button.mat-mdc-outlined-button:not(:disabled) { + color: var(--color-red-500) !important; + } + + &.mdc-button.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before { + background-color: var(--color-red-500) !important; + } +} + +.success-button { + @include mat.button-overrides(( + outlined-ripple-color: rgba(0, 255, 0, 0.1), + )); + + &.mdc-button:not(.mat-mdc-outlined-button):not(:disabled) { + background-color: var(--color-green-500) !important; + } + + &.mdc-button.mat-mdc-outlined-button:not(:disabled) { + color: var(--mat-sys-on-surface) !important; + } + + &.mdc-button.mat-mdc-outlined-button .mat-mdc-button-persistent-ripple::before { + background-color: var(--color-green-500) !important; + } +} + + + + body { // Default the application to a light color theme. This can be changed to // `dark` to enable the dark color theme, or to `light dark` to defer to the