├── .detective ├── config.json ├── hash └── log ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── apps ├── flights │ ├── .eslintrc.json │ ├── project.json │ ├── src │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.routes.ts │ │ │ ├── domains │ │ │ │ ├── checkin │ │ │ │ │ ├── data │ │ │ │ │ │ ├── checkin.service.ts │ │ │ │ │ │ ├── hidden.service.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── feature-manage │ │ │ │ │ │ ├── feature-manage.component.css │ │ │ │ │ │ ├── feature-manage.component.html │ │ │ │ │ │ ├── feature-manage.component.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── luggage │ │ │ │ │ ├── data │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── luggage.service.spec.ts │ │ │ │ │ │ ├── luggage.service.ts │ │ │ │ │ │ └── luggage.ts │ │ │ │ │ ├── feature-checkin │ │ │ │ │ │ ├── checkin │ │ │ │ │ │ │ ├── checkin.component.css │ │ │ │ │ │ │ ├── checkin.component.html │ │ │ │ │ │ │ ├── checkin.component.spec.ts │ │ │ │ │ │ │ └── checkin.component.ts │ │ │ │ │ │ ├── feature-checkin.routes.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── ui-common │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── luggage-card │ │ │ │ │ │ ├── luggage-card.component.css │ │ │ │ │ │ ├── luggage-card.component.html │ │ │ │ │ │ ├── luggage-card.component.spec.ts │ │ │ │ │ │ └── luggage-card.component.ts │ │ │ │ ├── shared │ │ │ │ │ ├── ui-common │ │ │ │ │ │ ├── city.pipe.ts │ │ │ │ │ │ ├── click-with-warning.directive.ts │ │ │ │ │ │ ├── confirm │ │ │ │ │ │ │ ├── confirm.component.css │ │ │ │ │ │ │ ├── confirm.component.html │ │ │ │ │ │ │ └── confirm.component.ts │ │ │ │ │ │ ├── custom-template-outlet.directive.ts │ │ │ │ │ │ ├── data-table │ │ │ │ │ │ │ ├── data-table.component.css │ │ │ │ │ │ │ ├── data-table.component.html │ │ │ │ │ │ │ ├── data-table.component.ts │ │ │ │ │ │ │ └── table-field.directive.ts │ │ │ │ │ │ ├── date │ │ │ │ │ │ │ ├── date-cva.directive.ts │ │ │ │ │ │ │ └── date-stepper │ │ │ │ │ │ │ │ ├── date-stepper.component.css │ │ │ │ │ │ │ │ ├── date-stepper.component.html │ │ │ │ │ │ │ │ └── date-stepper.component.ts │ │ │ │ │ │ ├── flight-card │ │ │ │ │ │ │ ├── flight-card.component.css │ │ │ │ │ │ │ ├── flight-card.component.html │ │ │ │ │ │ │ └── flight-card.component.ts │ │ │ │ │ │ ├── form-update.directive.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── status-toggle │ │ │ │ │ │ │ ├── status-toggle.component.css │ │ │ │ │ │ │ ├── status-toggle.component.html │ │ │ │ │ │ │ └── status-toggle.component.ts │ │ │ │ │ │ ├── tab-navigator │ │ │ │ │ │ │ ├── tab-navigator.component.css │ │ │ │ │ │ │ ├── tab-navigator.component.html │ │ │ │ │ │ │ └── tab-navigator.component.ts │ │ │ │ │ │ ├── tab │ │ │ │ │ │ │ ├── tab.component.css │ │ │ │ │ │ │ ├── tab.component.html │ │ │ │ │ │ │ └── tab.component.ts │ │ │ │ │ │ ├── tabbed-pane │ │ │ │ │ │ │ ├── tabbed-pane.component.css │ │ │ │ │ │ │ ├── tabbed-pane.component.html │ │ │ │ │ │ │ ├── tabbed-pane.component.ts │ │ │ │ │ │ │ └── tabbed-pane.service.ts │ │ │ │ │ │ └── tooltip.directive.ts │ │ │ │ │ ├── util-auth │ │ │ │ │ │ ├── auth.service.ts │ │ │ │ │ │ ├── auth.store.ts │ │ │ │ │ │ ├── auth.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── util-common │ │ │ │ │ │ ├── can-exit.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── with-data-service.feature.ts │ │ │ │ │ ├── util-config │ │ │ │ │ │ ├── config.service.ts │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── util-logger │ │ │ │ │ │ ├── color-config.ts │ │ │ │ │ │ ├── color.service.ts │ │ │ │ │ │ ├── color.ts │ │ │ │ │ │ ├── custom-log-appender.ts │ │ │ │ │ │ ├── features.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── log-appender.ts │ │ │ │ │ │ ├── log-formatter.ts │ │ │ │ │ │ ├── log-level.ts │ │ │ │ │ │ ├── logger-config.ts │ │ │ │ │ │ ├── logger.ts │ │ │ │ │ │ └── provider.ts │ │ │ │ │ └── util-validation │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── validation-errors │ │ │ │ │ │ ├── validation-errors.component.css │ │ │ │ │ │ ├── validation-errors.component.html │ │ │ │ │ │ └── validation-errors.component.ts │ │ │ │ │ │ └── validation │ │ │ │ │ │ ├── city-validator.directive.ts │ │ │ │ │ │ ├── city-validator.ts │ │ │ │ │ │ ├── roundtrip-validator.directive.ts │ │ │ │ │ │ └── roundtrip-validator.ts │ │ │ │ └── ticketing │ │ │ │ │ ├── data │ │ │ │ │ ├── flight-filter.ts │ │ │ │ │ ├── flight.service.ts │ │ │ │ │ ├── flight.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── ticket-filter.ts │ │ │ │ │ ├── ticket.service.ts │ │ │ │ │ └── ticket.ts │ │ │ │ │ ├── feature-booking │ │ │ │ │ ├── booking.store.ts │ │ │ │ │ ├── flight-booking.component.css │ │ │ │ │ ├── flight-booking.component.html │ │ │ │ │ ├── flight-booking.component.ts │ │ │ │ │ ├── flight-booking.routes.ts │ │ │ │ │ ├── flight-edit-reactive │ │ │ │ │ │ ├── flight-edit-reactive.component.css │ │ │ │ │ │ ├── flight-edit-reactive.component.html │ │ │ │ │ │ └── flight-edit-reactive.component.ts │ │ │ │ │ ├── flight-edit │ │ │ │ │ │ ├── flight-edit.component.css │ │ │ │ │ │ ├── flight-edit.component.html │ │ │ │ │ │ └── flight-edit.component.ts │ │ │ │ │ ├── flight-search │ │ │ │ │ │ ├── flight-search.component.css │ │ │ │ │ │ ├── flight-search.component.html │ │ │ │ │ │ └── flight-search.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── passenger-search │ │ │ │ │ │ ├── passenger-search.component.css │ │ │ │ │ │ ├── passenger-search.component.html │ │ │ │ │ │ └── passenger-search.component.ts │ │ │ │ │ └── feature-next-flights │ │ │ │ │ ├── checkin │ │ │ │ │ ├── checkin.component.css │ │ │ │ │ ├── checkin.component.html │ │ │ │ │ └── checkin.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── next-flights.component.css │ │ │ │ │ ├── next-flights.component.html │ │ │ │ │ ├── next-flights.component.ts │ │ │ │ │ ├── next-flights.module.ts │ │ │ │ │ ├── next-flights.routes.ts │ │ │ │ │ ├── next-flights.service.ts │ │ │ │ │ └── tickets.store.ts │ │ │ └── shell │ │ │ │ ├── about │ │ │ │ ├── about.component.css │ │ │ │ ├── about.component.html │ │ │ │ └── about.component.ts │ │ │ │ ├── basket │ │ │ │ ├── basket.component.css │ │ │ │ ├── basket.component.html │ │ │ │ └── basket.component.ts │ │ │ │ ├── home │ │ │ │ ├── home.component.css │ │ │ │ ├── home.component.html │ │ │ │ └── home.component.ts │ │ │ │ ├── navbar │ │ │ │ ├── navbar.component.html │ │ │ │ └── navbar.component.ts │ │ │ │ ├── not-found │ │ │ │ ├── not-found.component.css │ │ │ │ ├── not-found.component.html │ │ │ │ └── not-found.component.ts │ │ │ │ └── sidebar │ │ │ │ ├── sidebar.component.html │ │ │ │ └── sidebar.component.ts │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ ├── config.json │ │ │ └── paper-design │ │ │ │ └── angular2-logo.png │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ └── styles.css │ ├── tsconfig.app.json │ ├── tsconfig.editor.json │ ├── tsconfig.json │ └── tsconfig.spec.json └── miles │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── app.routes.ts │ │ └── nx-welcome.component.ts │ ├── assets │ │ └── .gitkeep │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── styles.css │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.editor.json │ ├── tsconfig.json │ └── tsconfig.spec.json ├── jest.config.ts ├── jest.preset.js ├── karma.conf.js ├── libs ├── .gitkeep └── ui-common │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── index.ts │ ├── lib │ │ └── ui-common │ │ │ ├── ui-common.component.css │ │ │ ├── ui-common.component.html │ │ │ ├── ui-common.component.spec.ts │ │ │ └── ui-common.component.ts │ └── test-setup.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json ├── sheriff.config.ts ├── tools ├── generators │ └── .gitkeep └── tsconfig.tools.json └── tsconfig.base.json /.detective/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopes": [ 3 | "apps/flights/src/app/domains/shared", 4 | "apps/flights/src/app/domains/luggage", 5 | "apps/flights/src/app/domains/checkin", 6 | "apps/flights/src/app/domains/ticketing" 7 | ], 8 | "groups": [ 9 | "apps/flights/src/app/domains", 10 | "apps/flights/src/app", 11 | "apps/flights/src", 12 | "apps/flights", 13 | "apps" 14 | ], 15 | "entries": [], 16 | "filter": { 17 | "files": [], 18 | "logs": [] 19 | }, 20 | "aliases": {}, 21 | "teams": { 22 | "example-team-a": [ 23 | "John Doe", 24 | "Jane Doe" 25 | ], 26 | "example-team-b": [ 27 | "Max Muster", 28 | "Susi Sorglos", 29 | "Manfred Steyer" 30 | ] 31 | }, 32 | "focus": "apps/flights/src/app/domains" 33 | } -------------------------------------------------------------------------------- /.detective/hash: -------------------------------------------------------------------------------- 1 | 47afa29b2d3e97dbefc625769a21d561faa8baf7, v1.2.3 -------------------------------------------------------------------------------- /.detective/log: -------------------------------------------------------------------------------- 1 | "Manfred Steyer ,Thu Oct 17 16:06:07 2024 +0200 ef503861b81c43f6fa60967fb5d3f3a0d5f5239b,prepare for ng-love" 2 | 34 0 .detective/config.json 3 | 1 0 .detective/hash 4 | 267 0 .detective/log 5 | 5 0 apps/flights/src/app/app.routes.ts 6 | 4 0 apps/flights/src/app/domains/checkin/data/checkin.service.ts 7 | 3 0 apps/flights/src/app/domains/luggage/data/luggage.service.ts 8 | 2 2 apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.html 9 | 13 4 apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.ts 10 | 124 54 package-lock.json 11 | 1 0 package.json 12 | 1 1 sheriff.config.ts 13 | 14 | "Manfred Steyer ,Thu Oct 17 15:20:49 2024 +0200 794fb55421a1ba3a54284f7a05abc4061f696667,fix dep issues" 15 | 3 2 apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.ts 16 | 17 | "Manfred Steyer ,Thu Oct 10 12:07:42 2024 +0200 c27aedc5e817f5b6b9cb7ed7ec776c0f928b3cdb,use store in store" 18 | 5 6 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.ts 19 | 34 22 apps/flights/src/app/domains/ticketing/feature-next-flights/tickets.store.ts 20 | 21 | "Manfred Steyer ,Tue Sep 17 19:56:59 2024 +0200 beb94ae3782a32c9c2212c1a6af87f61def5c07b,modify default user id" 22 | 1 1 apps/flights/src/app/domains/shared/util-auth/auth.store.ts 23 | 24 | "Manfred Steyer ,Thu May 23 13:22:44 2024 +0200 ce0860d486ac542e407ddc50a898b4d15561464c,add lib and app to demo nx features" 25 | 2 1 .prettierignore 26 | 2 1 .vscode/extensions.json 27 | 2 1 apps/flights/src/app/app.component.ts 28 | 2 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts 29 | 36 0 apps/miles/.eslintrc.json 30 | 22 0 apps/miles/jest.config.ts 31 | 79 0 apps/miles/project.json 32 | 0 0 apps/miles/src/app/app.component.css 33 | 2 0 apps/miles/src/app/app.component.html 34 | 27 0 apps/miles/src/app/app.component.spec.ts 35 | 15 0 apps/miles/src/app/app.component.ts 36 | 7 0 apps/miles/src/app/app.config.ts 37 | 3 0 apps/miles/src/app/app.routes.ts 38 | 888 0 apps/miles/src/app/nx-welcome.component.ts 39 | 0 0 apps/miles/src/assets/.gitkeep 40 | - - apps/miles/src/favicon.ico 41 | 13 0 apps/miles/src/index.html 42 | 7 0 apps/miles/src/main.ts 43 | 1 0 apps/miles/src/styles.css 44 | 8 0 apps/miles/src/test-setup.ts 45 | 10 0 apps/miles/tsconfig.app.json 46 | 7 0 apps/miles/tsconfig.editor.json 47 | 33 0 apps/miles/tsconfig.json 48 | 16 0 apps/miles/tsconfig.spec.json 49 | 5 0 jest.config.ts 50 | 3 0 jest.preset.js 51 | 36 0 libs/ui-common/.eslintrc.json 52 | 7 0 libs/ui-common/README.md 53 | 22 0 libs/ui-common/jest.config.ts 54 | 27 0 libs/ui-common/project.json 55 | 1 0 libs/ui-common/src/index.ts 56 | 0 0 libs/ui-common/src/lib/ui-common/ui-common.component.css 57 | 1 0 libs/ui-common/src/lib/ui-common/ui-common.component.html 58 | 21 0 libs/ui-common/src/lib/ui-common/ui-common.component.spec.ts 59 | 11 0 libs/ui-common/src/lib/ui-common/ui-common.component.ts 60 | 8 0 libs/ui-common/src/test-setup.ts 61 | 29 0 libs/ui-common/tsconfig.json 62 | 17 0 libs/ui-common/tsconfig.lib.json 63 | 16 0 libs/ui-common/tsconfig.spec.json 64 | 13 0 nx.json 65 | 2 1 tsconfig.base.json 66 | 67 | "Manfred Steyer ,Tue Apr 2 21:09:00 2024 +0200 f21de96d60a68bc091c751a1f96861b931b55593,add dev tools" 68 | 2 0 apps/flights/src/app/domains/shared/util-auth/auth.store.ts 69 | 3 1 apps/flights/src/app/domains/ticketing/feature-booking/booking.store.ts 70 | 3 1 apps/flights/src/app/domains/ticketing/feature-next-flights/tickets.store.ts 71 | 72 | "Manfred Steyer ,Tue Apr 2 16:35:06 2024 +0200 50dabe14d662bd7e07a3d34b0e27937cfbefdd14,coordinate stores" 73 | 0 0 apps/flights/src/app/domains/{ticketing/feature-booking => shared/ui-common}/flight-card/flight-card.component.css 74 | 0 0 apps/flights/src/app/domains/{ticketing/feature-booking => shared/ui-common}/flight-card/flight-card.component.html 75 | 1 1 apps/flights/src/app/domains/{ticketing/feature-booking => shared/ui-common}/flight-card/flight-card.component.ts 76 | 2 1 apps/flights/src/app/domains/shared/ui-common/index.ts 77 | 10 0 apps/flights/src/app/domains/shared/util-auth/auth.store.ts 78 | 1 0 apps/flights/src/app/domains/shared/util-auth/index.ts 79 | 3 0 apps/flights/src/app/domains/ticketing/data/index.ts 80 | 3 0 apps/flights/src/app/domains/ticketing/data/ticket-filter.ts 81 | 26 0 apps/flights/src/app/domains/ticketing/data/ticket.service.ts 82 | 12 0 apps/flights/src/app/domains/ticketing/data/ticket.ts 83 | 1 2 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts 84 | 1 0 apps/flights/src/app/domains/ticketing/feature-booking/index.ts 85 | 3 2 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.html 86 | 8 2 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.ts 87 | 5 4 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.module.ts 88 | 10 10 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.service.ts 89 | 37 0 apps/flights/src/app/domains/ticketing/feature-next-flights/tickets.store.ts 90 | 91 | "Manfred Steyer ,Tue Apr 2 15:17:16 2024 +0200 5bd76762b00051cf0b2a6dfefc15d4345f7b92da,add store with data service feature" 92 | 1 0 apps/flights/src/app/domains/shared/util-common/index.ts 93 | 53 0 apps/flights/src/app/domains/shared/util-common/with-data-service.feature.ts 94 | 0 2 apps/flights/src/app/domains/shared/util-validation/index.ts 95 | 0 33 apps/flights/src/app/domains/shared/util-validation/validation/async-city-validator.directive.ts 96 | 0 16 apps/flights/src/app/domains/shared/util-validation/validation/async-city-validator.ts 97 | 7 9 apps/flights/src/app/domains/ticketing/data/flight.service.ts 98 | 11 0 apps/flights/src/app/domains/ticketing/feature-booking/booking.store.ts 99 | 1 1 apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.routes.ts 100 | 0 2 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.ts 101 | 2 2 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.html 102 | 10 19 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts 103 | 104 | "Manfred Steyer ,Tue Apr 2 14:23:16 2024 +0200 1b5547d8e495f5754a0759f4081c4ec3a8e8b9c0,remove unneeded stuff" 105 | 14 0 apps/flights/src/app/domains/shared/ui-common/form-update.directive.ts 106 | 1 0 apps/flights/src/app/domains/shared/ui-common/index.ts 107 | 4 0 apps/flights/src/app/domains/ticketing/data/flight-filter.ts 108 | 1 0 apps/flights/src/app/domains/ticketing/data/index.ts 109 | 1 2 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.html 110 | 6 30 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts 111 | 112 | "Manfred Steyer ,Tue Apr 2 14:16:02 2024 +0200 dd0b7ff0f4d8cb79413f45c2f8a056ba46c9414d,init" 113 | 16 0 .editorconfig 114 | 1 0 .eslintignore 115 | 43 0 .eslintrc.json 116 | 46 0 .gitignore 117 | 4 0 .husky/pre-commit 118 | 4 0 .prettierignore 119 | 3 0 .prettierrc 120 | 8 0 .vscode/extensions.json 121 | 20 0 .vscode/launch.json 122 | 11 0 .vscode/settings.json 123 | 42 0 .vscode/tasks.json 124 | 27 0 README.md 125 | 42 0 apps/flights/.eslintrc.json 126 | 89 0 apps/flights/project.json 127 | 60 0 apps/flights/src/app/app.component.css 128 | 24 0 apps/flights/src/app/app.component.html 129 | 51 0 apps/flights/src/app/app.component.ts 130 | 63 0 apps/flights/src/app/app.routes.ts 131 | 10 0 apps/flights/src/app/domains/checkin/data/checkin.service.ts 132 | 8 0 apps/flights/src/app/domains/checkin/data/hidden.service.ts 133 | 1 0 apps/flights/src/app/domains/checkin/data/index.ts 134 | 3 0 apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.css 135 | 5 0 apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.html 136 | 22 0 apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.ts 137 | 3 0 apps/flights/src/app/domains/checkin/feature-manage/index.ts 138 | 2 0 apps/flights/src/app/domains/luggage/data/index.ts 139 | 16 0 apps/flights/src/app/domains/luggage/data/luggage.service.spec.ts 140 | 16 0 apps/flights/src/app/domains/luggage/data/luggage.service.ts 141 | 13 0 apps/flights/src/app/domains/luggage/data/luggage.ts 142 | 0 0 apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.css 143 | 9 0 apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.html 144 | 21 0 apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.spec.ts 145 | 23 0 apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.ts 146 | 10 0 apps/flights/src/app/domains/luggage/feature-checkin/feature-checkin.routes.ts 147 | 2 0 apps/flights/src/app/domains/luggage/feature-checkin/index.ts 148 | 1 0 apps/flights/src/app/domains/luggage/ui-common/index.ts 149 | 0 0 apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.css 150 | 11 0 apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.html 151 | 21 0 apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.spec.ts 152 | 14 0 apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.ts 153 | 34 0 apps/flights/src/app/domains/shared/ui-common/city.pipe.ts 154 | 41 0 apps/flights/src/app/domains/shared/ui-common/click-with-warning.directive.ts 155 | 0 0 apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.css 156 | 9 0 apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.html 157 | 19 0 apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.ts 158 | 37 0 apps/flights/src/app/domains/shared/ui-common/custom-template-outlet.directive.ts 159 | 0 0 apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.css 160 | 12 0 apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.html 161 | 22 0 apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.ts 162 | 11 0 apps/flights/src/app/domains/shared/ui-common/data-table/table-field.directive.ts 163 | 52 0 apps/flights/src/app/domains/shared/ui-common/date/date-cva.directive.ts 164 | 0 0 apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.css 165 | 3 0 apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.html 166 | 48 0 apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.ts 167 | 18 0 apps/flights/src/app/domains/shared/ui-common/index.ts 168 | 0 0 apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.css 169 | 1 0 apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.html 170 | 19 0 apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.ts 171 | 13 0 apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.css 172 | 5 0 apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.html 173 | 52 0 apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.ts 174 | 0 0 apps/flights/src/app/domains/shared/ui-common/tab/tab.component.css 175 | 6 0 apps/flights/src/app/domains/shared/ui-common/tab/tab.component.html 176 | 14 0 apps/flights/src/app/domains/shared/ui-common/tab/tab.component.ts 177 | 24 0 apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.css 178 | 22 0 apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.html 179 | 80 0 apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.ts 180 | 10 0 apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.service.ts 181 | 55 0 apps/flights/src/app/domains/shared/ui-common/tooltip.directive.ts 182 | 25 0 apps/flights/src/app/domains/shared/util-auth/auth.service.ts 183 | 14 0 apps/flights/src/app/domains/shared/util-auth/auth.ts 184 | 6 0 apps/flights/src/app/domains/shared/util-auth/index.ts 185 | 5 0 apps/flights/src/app/domains/shared/util-common/can-exit.ts 186 | 1 0 apps/flights/src/app/domains/shared/util-common/index.ts 187 | 28 0 apps/flights/src/app/domains/shared/util-config/config.service.ts 188 | 7 0 apps/flights/src/app/domains/shared/util-config/config.ts 189 | 6 0 apps/flights/src/app/domains/shared/util-config/index.ts 190 | 12 0 apps/flights/src/app/domains/shared/util-logger/color-config.ts 191 | 22 0 apps/flights/src/app/domains/shared/util-logger/color.service.ts 192 | 21 0 apps/flights/src/app/domains/shared/util-logger/color.ts 193 | 12 0 apps/flights/src/app/domains/shared/util-logger/custom-log-appender.ts 194 | 12 0 apps/flights/src/app/domains/shared/util-logger/features.ts 195 | 15 0 apps/flights/src/app/domains/shared/util-logger/index.ts 196 | 21 0 apps/flights/src/app/domains/shared/util-logger/log-appender.ts 197 | 15 0 apps/flights/src/app/domains/shared/util-logger/log-formatter.ts 198 | 5 0 apps/flights/src/app/domains/shared/util-logger/log-level.ts 199 | 16 0 apps/flights/src/app/domains/shared/util-logger/logger-config.ts 200 | 45 0 apps/flights/src/app/domains/shared/util-logger/logger.ts 201 | 38 0 apps/flights/src/app/domains/shared/util-logger/provider.ts 202 | 11 0 apps/flights/src/app/domains/shared/util-validation/index.ts 203 | 0 0 apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.css 204 | 3 0 apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.html 205 | 22 0 apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.ts 206 | 33 0 apps/flights/src/app/domains/shared/util-validation/validation/async-city-validator.directive.ts 207 | 16 0 apps/flights/src/app/domains/shared/util-validation/validation/async-city-validator.ts 208 | 34 0 apps/flights/src/app/domains/shared/util-validation/validation/city-validator.directive.ts 209 | 15 0 apps/flights/src/app/domains/shared/util-validation/validation/city-validator.ts 210 | 40 0 apps/flights/src/app/domains/shared/util-validation/validation/roundtrip-validator.directive.ts 211 | 22 0 apps/flights/src/app/domains/shared/util-validation/validation/roundtrip-validator.ts 212 | 41 0 apps/flights/src/app/domains/ticketing/data/flight.service.ts 213 | 15 0 apps/flights/src/app/domains/ticketing/data/flight.ts 214 | 2 0 apps/flights/src/app/domains/ticketing/data/index.ts 215 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.css 216 | 17 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.html 217 | 12 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.ts 218 | 42 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.routes.ts 219 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-card/flight-card.component.css 220 | 33 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-card/flight-card.component.html 221 | 62 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-card/flight-card.component.ts 222 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.css 223 | 30 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.html 224 | 67 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.ts 225 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.css 226 | 26 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.html 227 | 44 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.ts 228 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.css 229 | 38 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.html 230 | 73 0 apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts 231 | 1 0 apps/flights/src/app/domains/ticketing/feature-booking/index.ts 232 | 0 0 apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.css 233 | 1 0 apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.html 234 | 16 0 apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.ts 235 | 0 0 apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.css 236 | 1 0 apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.html 237 | 8 0 apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.ts 238 | 1 0 apps/flights/src/app/domains/ticketing/feature-next-flights/index.ts 239 | 0 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.css 240 | 4 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.html 241 | 12 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.ts 242 | 15 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.module.ts 243 | 10 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.routes.ts 244 | 16 0 apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.service.ts 245 | 0 0 apps/flights/src/app/shell/about/about.component.css 246 | 3 0 apps/flights/src/app/shell/about/about.component.html 247 | 11 0 apps/flights/src/app/shell/about/about.component.ts 248 | 25 0 apps/flights/src/app/shell/basket/basket.component.css 249 | 25 0 apps/flights/src/app/shell/basket/basket.component.html 250 | 12 0 apps/flights/src/app/shell/basket/basket.component.ts 251 | 0 0 apps/flights/src/app/shell/home/home.component.css 252 | 8 0 apps/flights/src/app/shell/home/home.component.html 253 | 22 0 apps/flights/src/app/shell/home/home.component.ts 254 | 129 0 apps/flights/src/app/shell/navbar/navbar.component.html 255 | 22 0 apps/flights/src/app/shell/navbar/navbar.component.ts 256 | 0 0 apps/flights/src/app/shell/not-found/not-found.component.css 257 | 1 0 apps/flights/src/app/shell/not-found/not-found.component.html 258 | 11 0 apps/flights/src/app/shell/not-found/not-found.component.ts 259 | 51 0 apps/flights/src/app/shell/sidebar/sidebar.component.html 260 | 10 0 apps/flights/src/app/shell/sidebar/sidebar.component.ts 261 | 0 0 apps/flights/src/assets/.gitkeep 262 | 3 0 apps/flights/src/assets/config.json 263 | - - apps/flights/src/assets/paper-design/angular2-logo.png 264 | - - apps/flights/src/favicon.ico 265 | 36 0 apps/flights/src/index.html 266 | 25 0 apps/flights/src/main.ts 267 | 38 0 apps/flights/src/styles.css 268 | 9 0 apps/flights/tsconfig.app.json 269 | 7 0 apps/flights/tsconfig.editor.json 270 | 32 0 apps/flights/tsconfig.json 271 | 8 0 apps/flights/tsconfig.spec.json 272 | 43 0 karma.conf.js 273 | 0 0 libs/.gitkeep 274 | 63 0 nx.json 275 | 25758 0 package-lock.json 276 | 99 0 package.json 277 | 30 0 sheriff.config.ts 278 | 0 0 tools/generators/.gitkeep 279 | 12 0 tools/tsconfig.tools.json 280 | 34 0 tsconfig.base.json 281 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@angular-eslint/recommended", 11 | "plugin:@angular-eslint/template/process-inline-templates" 12 | ], 13 | "rules": { 14 | "@angular-eslint/directive-selector": [ 15 | "error", 16 | { 17 | "type": "attribute", 18 | "prefix": "app", 19 | "style": "camelCase" 20 | } 21 | ], 22 | "@angular-eslint/component-selector": [ 23 | "error", 24 | { 25 | "type": "element", 26 | "prefix": "app", 27 | "style": "kebab-case" 28 | } 29 | ] 30 | } 31 | }, 32 | { 33 | "files": ["*.html"], 34 | "extends": ["plugin:@angular-eslint/template/recommended"], 35 | "rules": {} 36 | }, 37 | { 38 | "files": ["*.ts"], 39 | "extends": ["plugin:@softarc/sheriff/legacy"] 40 | } 41 | ], 42 | "plugins": ["@nx"] 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | .wireit 44 | 45 | .nx 46 | .angular -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | nx lint -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache 5 | .angular 6 | 7 | /.nx/workspace-data -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "angular.ng-template", 4 | "nrwl.angular-console", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode", 7 | "firsttris.vscode-jest-runner" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.svn": true, 5 | "**/.hg": true, 6 | "**/CVS": true, 7 | "**/.DS_Store": true, 8 | "**/Thumbs.db": true 9 | }, 10 | "hide-files.files": [] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flights 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.0.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /apps/flights/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": ["!**/*"], 3 | "overrides": [ 4 | { 5 | "files": ["*.ts"], 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:@angular-eslint/recommended", 10 | "plugin:@angular-eslint/template/process-inline-templates" 11 | ], 12 | "rules": { 13 | "@angular-eslint/directive-selector": [ 14 | "error", 15 | { 16 | "type": "attribute", 17 | "prefix": "app", 18 | "style": "camelCase" 19 | } 20 | ], 21 | "@angular-eslint/component-selector": [ 22 | "error", 23 | { 24 | "type": "element", 25 | "prefix": "app", 26 | "style": "kebab-case" 27 | } 28 | ], 29 | "@angular-eslint/prefer-standalone": "off" 30 | } 31 | }, 32 | { 33 | "files": ["*.html"], 34 | "extends": ["plugin:@angular-eslint/template/recommended"], 35 | "rules": {} 36 | }, 37 | { 38 | "files": ["*.ts"], 39 | "extends": ["plugin:@softarc/sheriff/legacy"] 40 | } 41 | ], 42 | "extends": "../../.eslintrc.json" 43 | } 44 | -------------------------------------------------------------------------------- /apps/flights/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flights", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "sourceRoot": "apps/flights/src", 6 | "prefix": "app", 7 | "generators": {}, 8 | "targets": { 9 | "build": { 10 | "executor": "@angular-devkit/build-angular:application", 11 | "options": { 12 | "outputPath": "dist/apps/flights", 13 | "index": "apps/flights/src/index.html", 14 | "browser": "apps/flights/src/main.ts", 15 | "polyfills": ["zone.js"], 16 | "tsConfig": "apps/flights/tsconfig.app.json", 17 | "assets": ["apps/flights/src/favicon.ico", "apps/flights/src/assets"], 18 | "styles": [ 19 | "@angular/material/prebuilt-themes/indigo-pink.css", 20 | "apps/flights/src/styles.css" 21 | ], 22 | "scripts": [] 23 | }, 24 | "configurations": { 25 | "production": { 26 | "outputHashing": "all" 27 | }, 28 | "development": { 29 | "optimization": false, 30 | "extractLicenses": false, 31 | "sourceMap": true, 32 | "namedChunks": true 33 | } 34 | }, 35 | "defaultConfiguration": "production" 36 | }, 37 | "serve": { 38 | "executor": "@angular-devkit/build-angular:dev-server", 39 | "configurations": { 40 | "production": { 41 | "buildTarget": "flights:build:production" 42 | }, 43 | "development": { 44 | "buildTarget": "flights:build:development" 45 | } 46 | }, 47 | "defaultConfiguration": "development" 48 | }, 49 | "extract-i18n": { 50 | "executor": "@angular-devkit/build-angular:extract-i18n", 51 | "options": { 52 | "browserTarget": "flights:build" 53 | } 54 | }, 55 | "test": { 56 | "executor": "@angular-devkit/build-angular:karma", 57 | "options": { 58 | "polyfills": ["zone.js", "zone.js/testing"], 59 | "tsConfig": "apps/flights/tsconfig.spec.json", 60 | "assets": ["apps/flights/src/favicon.ico", "apps/flights/src/assets"], 61 | "styles": [ 62 | "@angular/material/prebuilt-themes/indigo-pink.css", 63 | "apps/flights/src/styles.css" 64 | ], 65 | "scripts": [] 66 | } 67 | }, 68 | "lint": { 69 | "executor": "@nx/eslint:lint" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /apps/flights/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .loading-indicator { 2 | position: fixed; 3 | left: 0px; 4 | top: 0px; 5 | width: 100%; 6 | height: 100%; 7 | background-color: black; 8 | opacity: 0.3; 9 | z-index: 1000; 10 | } 11 | 12 | .spinner { 13 | width: 40px; 14 | height: 40px; 15 | 16 | position: relative; 17 | margin: 100px auto; 18 | } 19 | 20 | .double-bounce1, 21 | .double-bounce2 { 22 | width: 100%; 23 | height: 100%; 24 | border-radius: 50%; 25 | background-color: #fff; 26 | opacity: 0.6; 27 | position: absolute; 28 | top: 0; 29 | left: 0; 30 | 31 | -webkit-animation: sk-bounce 2s infinite ease-in-out; 32 | animation: sk-bounce 2s infinite ease-in-out; 33 | } 34 | 35 | .double-bounce2 { 36 | -webkit-animation-delay: -1s; 37 | animation-delay: -1s; 38 | } 39 | 40 | @-webkit-keyframes sk-bounce { 41 | 0%, 42 | 100% { 43 | -webkit-transform: scale(0); 44 | } 45 | 50% { 46 | -webkit-transform: scale(1); 47 | } 48 | } 49 | 50 | @keyframes sk-bounce { 51 | 0%, 52 | 100% { 53 | transform: scale(0); 54 | -webkit-transform: scale(0); 55 | } 56 | 50% { 57 | transform: scale(1); 58 | -webkit-transform: scale(1); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /apps/flights/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 6 |
7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 |
16 | 17 | @if(loading$ | async) { 18 |
19 |
20 |
21 |
22 |
23 |
24 | } 25 | -------------------------------------------------------------------------------- /apps/flights/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { AsyncPipe } from '@angular/common'; 2 | import { Component, inject } from '@angular/core'; 3 | import { 4 | NavigationCancel, 5 | NavigationEnd, 6 | NavigationError, 7 | NavigationStart, 8 | Router, 9 | RouterOutlet, 10 | } from '@angular/router'; 11 | import { filter, map, merge, Observable } from 'rxjs'; 12 | import { SidebarComponent } from './shell/sidebar/sidebar.component'; 13 | import { NavbarComponent } from './shell/navbar/navbar.component'; 14 | import { ConfigService } from '@demo/shared/util-config'; 15 | 16 | @Component({ 17 | imports: [SidebarComponent, NavbarComponent, RouterOutlet, AsyncPipe], 18 | selector: 'app-root', 19 | templateUrl: './app.component.html', 20 | styleUrls: ['./app.component.css'] 21 | }) 22 | export class AppComponent { 23 | title = 'Hello World!'; 24 | configService = inject(ConfigService); 25 | router = inject(Router); 26 | loading$: Observable; 27 | 28 | constructor() { 29 | // TODO: In a later lab, we will assure that 30 | // loading did happen _before_ we use the config! 31 | this.configService.loadConfig(); 32 | 33 | const stop$ = this.router.events.pipe( 34 | filter( 35 | (e) => 36 | e instanceof NavigationEnd || 37 | e instanceof NavigationError || 38 | e instanceof NavigationCancel 39 | ), 40 | map(() => false) 41 | ); 42 | 43 | const start$ = this.router.events.pipe( 44 | filter((e) => e instanceof NavigationStart), 45 | map(() => true) 46 | ); 47 | 48 | this.loading$ = merge(start$, stop$); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/flights/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { Routes } from '@angular/router'; 3 | import { AboutComponent } from './shell/about/about.component'; 4 | import { HomeComponent } from './shell/home/home.component'; 5 | import { BasketComponent } from './shell/basket/basket.component'; 6 | import { ConfigService } from '@demo/shared/util-config'; 7 | import { NotFoundComponent } from './shell/not-found/not-found.component'; 8 | import { FeatureManageComponent } from '@demo/checkin/feature-manage'; 9 | 10 | export const APP_ROUTES: Routes = [ 11 | { 12 | path: '', 13 | pathMatch: 'full', 14 | redirectTo: 'home', 15 | }, 16 | { 17 | path: 'home', 18 | component: HomeComponent, 19 | }, 20 | { 21 | path: 'checkin', 22 | component: FeatureManageComponent 23 | }, 24 | { 25 | path: 'basket', 26 | component: BasketComponent, 27 | outlet: 'aux', 28 | }, 29 | { 30 | path: '', 31 | resolve: { 32 | config: () => inject(ConfigService).loaded$, 33 | }, 34 | children: [ 35 | { 36 | path: 'luggage', 37 | loadChildren: () => 38 | import('./domains/luggage/feature-checkin').then( 39 | (m) => m.FEATURE_CHECKIN_ROUTES 40 | ), 41 | }, 42 | { 43 | path: 'flight-booking', 44 | loadChildren: () => 45 | import('@demo/ticketing/feature-booking').then( 46 | (m) => m.FLIGHT_BOOKING_ROUTES 47 | ), 48 | }, 49 | { 50 | path: 'next-flights', 51 | loadChildren: () => 52 | import('@demo/ticketing/feature-next-flights').then( 53 | (m) => m.NextFlightsModule 54 | ), 55 | }, 56 | { 57 | path: 'about', 58 | component: AboutComponent, 59 | }, 60 | 61 | // This _needs_ to be the last route!! 62 | { 63 | path: '**', 64 | component: NotFoundComponent, 65 | }, 66 | ], 67 | }, 68 | ]; 69 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/data/checkin.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { AuthService } from '@demo/shared/util-auth'; 4 | 5 | console.log(AuthService); 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class CheckinService { 11 | checkin(ticketNumber: string): void { 12 | console.log('checking in', ticketNumber); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/data/hidden.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ providedIn: 'root' }) 4 | export class HiddenService { 5 | doInternalStuff() { 6 | return 4711; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './checkin.service'; 2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.css: -------------------------------------------------------------------------------- 1 | .form-control { 2 | max-width: 300px; 3 | } 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.html: -------------------------------------------------------------------------------- 1 |

Checkin

2 | 3 |
Ticket Number:
4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/feature-manage/feature-manage.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { CheckinService } from '../data'; 5 | 6 | // import { } from '../../ticketing/data'; 7 | 8 | @Component({ 9 | selector: 'app-feature-manage', 10 | imports: [CommonModule, FormsModule], 11 | templateUrl: './feature-manage.component.html', 12 | styleUrls: ['./feature-manage.component.css'] 13 | }) 14 | export class FeatureManageComponent { 15 | service = inject(CheckinService); 16 | ticketNumber = ''; 17 | 18 | checkin() { 19 | this.service.checkin(this.ticketNumber); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/checkin/feature-manage/index.ts: -------------------------------------------------------------------------------- 1 | // Public API 2 | 3 | export * from './feature-manage.component'; 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './luggage'; 2 | export * from './luggage.service'; 3 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/data/luggage.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { LuggageService } from './luggage.service'; 4 | 5 | describe('LuggageService', () => { 6 | let service: LuggageService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(LuggageService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/data/luggage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Luggage } from './luggage'; 3 | import { Observable, of } from 'rxjs'; 4 | import { AuthService } from '@demo/shared/util-auth'; 5 | 6 | console.log(AuthService); 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class LuggageService { 12 | load(): Observable { 13 | return of([ 14 | { id: 1, weight: 4000, checkedIn: true, remarks: 'black trolly' }, 15 | { id: 2, weight: 7000, checkedIn: true, remarks: 'blue trolly' }, 16 | { id: 3, weight: 3000, checkedIn: true, remarks: 'yellow trolly' }, 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/data/luggage.ts: -------------------------------------------------------------------------------- 1 | export interface Luggage { 2 | id: number; 3 | weight: number; 4 | checkedIn: boolean; 5 | remarks: string; 6 | } 7 | 8 | export const initialLuggage: Luggage = { 9 | id: 0, 10 | weight: 0, 11 | checkedIn: false, 12 | remarks: '', 13 | }; 14 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.html: -------------------------------------------------------------------------------- 1 |

Checkin Luggage

2 | 3 |
4 | @for (item of luggage; track item.id) { 5 |
6 | 7 |
8 | } 9 |
10 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CheckinComponent } from './checkin.component'; 4 | 5 | describe('CheckinComponent', () => { 6 | let component: CheckinComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [CheckinComponent], 12 | }); 13 | fixture = TestBed.createComponent(CheckinComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/checkin/checkin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { Luggage, LuggageService } from '@demo/luggage/data'; 5 | import { LuggageCardComponent } from '@demo/luggage/ui-common'; 6 | 7 | @Component({ 8 | selector: 'app-checkin', 9 | imports: [CommonModule, LuggageCardComponent], 10 | templateUrl: './checkin.component.html', 11 | styleUrls: ['./checkin.component.css'] 12 | }) 13 | export class CheckinComponent implements OnInit { 14 | luggageService = inject(LuggageService); 15 | luggage: Luggage[] = []; 16 | 17 | ngOnInit(): void { 18 | this.luggageService.load().subscribe((luggage) => { 19 | this.luggage = luggage; 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/feature-checkin.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { CheckinComponent } from './checkin/checkin.component'; 3 | 4 | export const FEATURE_CHECKIN_ROUTES: Routes = [ 5 | { 6 | path: 'checkin', 7 | component: CheckinComponent, 8 | }, 9 | // { ... }, { ... } 10 | ]; 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/feature-checkin/index.ts: -------------------------------------------------------------------------------- 1 | // export * from './checkin/checkin.component'; 2 | export * from './feature-checkin.routes'; 3 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/ui-common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './luggage-card/luggage-card.component'; 2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Luggage #{{ luggageItem.id }}

4 |
5 | 6 |
7 |

Weight: #{{ luggageItem.weight }}

8 |

Checked in: #{{ luggageItem.checkedIn }}

9 |

Remarks: #{{ luggageItem.remarks }}

10 |
11 |
12 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LuggageCardComponent } from './luggage-card.component'; 4 | 5 | describe('LuggageCardComponent', () => { 6 | let component: LuggageCardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [LuggageCardComponent], 12 | }); 13 | fixture = TestBed.createComponent(LuggageCardComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/luggage/ui-common/luggage-card/luggage-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { initialLuggage } from '../../data'; 4 | 5 | @Component({ 6 | selector: 'app-luggage-card', 7 | imports: [CommonModule], 8 | templateUrl: './luggage-card.component.html', 9 | styleUrls: ['./luggage-card.component.css'] 10 | }) 11 | export class LuggageCardComponent { 12 | @Input() luggageItem = initialLuggage; 13 | } 14 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/city.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | export type CityFormat = 'long' | 'short'; 4 | 5 | @Pipe({ 6 | name: 'city', 7 | standalone: true, 8 | pure: true, 9 | }) 10 | export class CityPipe implements PipeTransform { 11 | transform(value: string, fmt?: CityFormat): string { 12 | let short, long; 13 | 14 | switch (value) { 15 | case 'Paris': 16 | short = 'CDG'; 17 | long = 'Charles de Gaulle Airport'; 18 | break; 19 | case 'London': 20 | short = 'LCY'; 21 | long = 'London City Airport'; 22 | break; 23 | case 'Berlin': 24 | short = 'BER'; 25 | long = 'Flughafen Berlin Brandenburg - Willy Brandt'; 26 | break; 27 | default: 28 | short = long = value; 29 | } 30 | 31 | if (fmt == 'short') return short; 32 | return long; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/click-with-warning.directive.ts: -------------------------------------------------------------------------------- 1 | import { Dialog } from '@angular/cdk/dialog'; 2 | import { 3 | Directive, 4 | EventEmitter, 5 | HostBinding, 6 | HostListener, 7 | inject, 8 | Input, 9 | Output, 10 | } from '@angular/core'; 11 | import { ConfirmComponent } from './confirm/confirm.component'; 12 | 13 | @Directive({ 14 | selector: '[appClickWithWarning]', 15 | exportAs: 'clickWithWarning', 16 | standalone: true, 17 | }) 18 | export class ClickWithWarningDirective { 19 | @Input() warning = 'Are you sure?'; 20 | @Output() appClickWithWarning = new EventEmitter(); 21 | 22 | @HostBinding('class') classBinding = 'btn btn-danger'; 23 | 24 | dialog = inject(Dialog); 25 | 26 | @HostListener('click', ['$event.shiftKey']) 27 | handleClick(shiftKey: boolean): void { 28 | if (shiftKey) { 29 | this.appClickWithWarning.emit(); 30 | } 31 | 32 | const ref = this.dialog.open(ConfirmComponent, { 33 | data: this.warning, 34 | }); 35 | ref.closed.subscribe((result) => { 36 | if (result) { 37 | this.appClickWithWarning.emit(); 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ message }} 5 |

6 | 7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/confirm/confirm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog'; 4 | 5 | @Component({ 6 | selector: 'app-confirm', 7 | imports: [CommonModule], 8 | templateUrl: './confirm.component.html', 9 | styleUrls: ['./confirm.component.css'] 10 | }) 11 | export class ConfirmComponent { 12 | message = inject(DIALOG_DATA) as string; 13 | dialogRef = inject(DialogRef) as DialogRef; 14 | 15 | close(decision: boolean): void { 16 | this.dialogRef.close(decision); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/custom-template-outlet.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | inject, 4 | Input, 5 | OnInit, 6 | TemplateRef, 7 | ViewContainerRef, 8 | } from '@angular/core'; 9 | 10 | @Directive({ 11 | selector: '[appCustomTemplateOutlet]', 12 | standalone: true, 13 | }) 14 | export class CustomTemplateOutletDirective implements OnInit { 15 | @Input('appCustomTemplateOutlet') template: TemplateRef | undefined; 16 | @Input('appCustomTemplateOutletContext') context: 17 | | Record 18 | | undefined; 19 | 20 | viewContainer = inject(ViewContainerRef); 21 | 22 | ngOnInit(): void { 23 | if (!this.template) { 24 | return; 25 | } 26 | this.viewContainer.clear(); 27 | 28 | const ref = this.viewContainer.createEmbeddedView( 29 | this.template, 30 | this.context 31 | ); 32 | 33 | // Get first projected node 34 | const nativeElement = ref.rootNodes[0]; 35 | console.log('nativeElement', nativeElement); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 |
4 | 10 |
13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/data-table/data-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ContentChildren, Input, QueryList } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { TableFieldDirective } from './table-field.directive'; 4 | import { CustomTemplateOutletDirective } from '../custom-template-outlet.directive'; 5 | 6 | @Component({ 7 | selector: 'app-data-table', 8 | imports: [CommonModule, CustomTemplateOutletDirective], 9 | templateUrl: './data-table.component.html', 10 | styleUrls: ['./data-table.component.css'] 11 | }) 12 | export class DataTableComponent { 13 | @Input() data: Array> = []; 14 | 15 | @ContentChildren(TableFieldDirective) 16 | fields: QueryList | undefined; 17 | 18 | get fieldList() { 19 | return this.fields?.toArray(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/data-table/table-field.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, inject, Input, TemplateRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[appTableField]', 5 | standalone: true, 6 | }) 7 | export class TableFieldDirective { 8 | // eslint-disable-next-line @angular-eslint/no-input-rename 9 | @Input('appTableFieldAs') propName = ''; 10 | templateRef = inject(TemplateRef) as TemplateRef; 11 | } 12 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/date/date-cva.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostBinding, HostListener } from '@angular/core'; 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | import { format, parse } from 'date-fns'; 4 | 5 | type OnChange = (value: string) => void; 6 | type OnTouched = () => void; 7 | 8 | @Directive({ 9 | selector: '[appDateCva]', 10 | standalone: true, 11 | providers: [ 12 | { 13 | provide: NG_VALUE_ACCESSOR, 14 | useExisting: DateCvaDirective, 15 | multi: true, 16 | }, 17 | ], 18 | }) 19 | export class DateCvaDirective implements ControlValueAccessor { 20 | _onChange: OnChange = () => { 21 | }; 22 | _onTouched: OnTouched = () => { 23 | }; 24 | 25 | @HostBinding('value') 26 | value = ''; 27 | 28 | @HostListener('change', ['$event.target.value']) 29 | change(value: string): void { 30 | const date = value ? parse(value, 'dd.MM.yyyy HH:mm', 0) : new Date(); 31 | this._onChange(date.toISOString()); 32 | } 33 | 34 | @HostListener('blur') 35 | blur(): void { 36 | this._onTouched(); 37 | } 38 | 39 | writeValue(dateISO: string): void { 40 | const date = new Date(dateISO); 41 | const formatted = date ? format(date, 'dd.MM.yyyy HH:mm') : ''; 42 | this.value = formatted; 43 | } 44 | registerOnChange(fn: OnChange): void { 45 | this._onChange = fn; 46 | } 47 | registerOnTouched(fn: OnTouched): void { 48 | this._onTouched = fn; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{ date | date : 'dd.MM.yyyy HH:mm' }} 3 | 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/date/date-stepper/date-stepper.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { addDays, subDays } from 'date-fns'; 4 | import { ControlValueAccessor, NgControl } from '@angular/forms'; 5 | 6 | type OnChange = (value: Date) => void; 7 | 8 | @Component({ 9 | selector: 'app-date-stepper', 10 | imports: [CommonModule], 11 | templateUrl: './date-stepper.component.html', 12 | styleUrls: ['./date-stepper.component.css'] 13 | }) 14 | export class DateStepperComponent implements ControlValueAccessor { 15 | ngControl = inject(NgControl); 16 | date = new Date(); 17 | _onChange: OnChange = () => { 18 | }; 19 | 20 | constructor() { 21 | this.ngControl.valueAccessor = this; 22 | } 23 | 24 | writeValue(date: Date): void { 25 | this.date = date ?? new Date(); 26 | } 27 | 28 | registerOnChange(fn: OnChange): void { 29 | this._onChange = fn; 30 | } 31 | 32 | registerOnTouched(): void { 33 | // Not used here 34 | } 35 | 36 | next(): void { 37 | this.date = addDays(this.date, 1); 38 | this._onChange(this.date); 39 | } 40 | 41 | prev(): void { 42 | this.date = subDays(this.date, 1); 43 | this._onChange(this.date); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ item.from | city : 'short' }} - {{ item.to | city : 'long' }} 5 |

6 |
7 | 8 |
9 |

Flight-No.: #{{ item.id }}

10 |

Date: {{ item.date | date : 'dd.MM.yyyy HH:mm' }}

11 | 12 | 13 | 14 |

15 | Delayed: 16 | 17 |

18 |

19 | @if (!selected) { 20 | 21 | } @if(selected) { 22 | 23 | } 24 | 30 |

31 | 32 |
33 |
34 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/flight-card/flight-card.component.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @softarc/sheriff/deep-import */ 2 | import { 3 | Component, 4 | ElementRef, 5 | EventEmitter, 6 | inject, 7 | Input, 8 | NgZone, 9 | Output, 10 | } from '@angular/core'; 11 | import { MatDialog } from '@angular/material/dialog'; 12 | import { CommonModule } from '@angular/common'; 13 | import { CityPipe } from '../city.pipe'; 14 | import { StatusToggleComponent } from '../status-toggle/status-toggle.component'; 15 | // eslint-disable-next-line @softarc/sheriff/dependency-rule 16 | 17 | const initFlight = { 18 | id: 0, 19 | from: '', 20 | to: '', 21 | date: '', 22 | delayed: false, 23 | }; 24 | 25 | @Component({ 26 | selector: 'app-flight-card', 27 | imports: [CommonModule, CityPipe, StatusToggleComponent], 28 | templateUrl: './flight-card.component.html', 29 | styleUrls: ['./flight-card.component.css'] 30 | }) 31 | export class FlightCardComponent { 32 | private element = inject(ElementRef); 33 | private zone = inject(NgZone); 34 | 35 | private dialog = inject(MatDialog); 36 | 37 | @Input() item = initFlight; 38 | @Input() selected = false; 39 | @Output() selectedChange = new EventEmitter(); 40 | 41 | select() { 42 | this.selected = true; 43 | this.selectedChange.emit(this.selected); 44 | } 45 | 46 | deselect() { 47 | this.selected = false; 48 | this.selectedChange.emit(this.selected); 49 | } 50 | 51 | edit() { 52 | // this.dialog.open(FlightEditReactiveComponent, { 53 | // data: { flight: this.item }, 54 | // }); 55 | } 56 | 57 | blink() { 58 | // Dirty Hack used to visualize the change detector 59 | this.element.nativeElement.firstChild.style.backgroundColor = 'crimson'; 60 | 61 | this.zone.runOutsideAngular(() => { 62 | setTimeout(() => { 63 | this.element.nativeElement.firstChild.style.backgroundColor = 'white'; 64 | }, 1000); 65 | }); 66 | 67 | return null; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/form-update.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, EventEmitter, Output, inject } from '@angular/core'; 2 | import { NgForm } from '@angular/forms'; 3 | 4 | @Directive({ selector: 'form[appUpdate]', standalone: true }) 5 | export class FormUpdateDirective { 6 | private ngForm = inject(NgForm); 7 | 8 | @Output() 9 | appUpdate = new EventEmitter(); 10 | 11 | constructor() { 12 | this.ngForm.valueChanges?.subscribe((value) => this.appUpdate.emit(value)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './city.pipe'; 6 | export * from './click-with-warning.directive'; 7 | export * from './custom-template-outlet.directive'; 8 | export * from './tooltip.directive'; 9 | export * from './confirm/confirm.component'; 10 | export * from './data-table/data-table.component'; 11 | export * from './data-table/table-field.directive'; 12 | export * from './date/date-cva.directive'; 13 | export * from './date/date-stepper/date-stepper.component'; 14 | export * from './status-toggle/status-toggle.component'; 15 | export * from './tab/tab.component'; 16 | export * from './tab-navigator/tab-navigator.component'; 17 | export * from './tabbed-pane/tabbed-pane.component'; 18 | export * from './tabbed-pane/tabbed-pane.service'; 19 | export * from './form-update.directive'; 20 | export * from './flight-card/flight-card.component'; 21 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.html: -------------------------------------------------------------------------------- 1 | {{ status }} 2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/status-toggle/status-toggle.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'app-status-toggle', 6 | imports: [CommonModule], 7 | templateUrl: './status-toggle.component.html', 8 | styleUrls: ['./status-toggle.component.css'] 9 | }) 10 | export class StatusToggleComponent { 11 | @Input() status = false; 12 | @Output() statusChange = new EventEmitter(); 13 | 14 | toggle(): void { 15 | this.status = !this.status; 16 | this.statusChange.next(this.status); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.css: -------------------------------------------------------------------------------- 1 | .tab-navigator { 2 | border: 2px solid black; 3 | width: 150px; 4 | } 5 | 6 | .tab-navigator button { 7 | border: none; 8 | background-color: inherit; 9 | } 10 | 11 | .tab-navigator .next { 12 | float: right; 13 | } 14 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | # {{ page + 1 }} / {{ pageCount }} 4 | 5 |
6 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab-navigator/tab-navigator.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | EventEmitter, 4 | inject, 5 | Input, 6 | OnInit, 7 | Output, 8 | } from '@angular/core'; 9 | import { CommonModule } from '@angular/common'; 10 | import { TabbedPaneService } from '../tabbed-pane/tabbed-pane.service'; 11 | 12 | @Component({ 13 | selector: 'app-tab-navigator', 14 | imports: [CommonModule], 15 | templateUrl: './tab-navigator.component.html', 16 | styleUrls: ['./tab-navigator.component.css'] 17 | }) 18 | export class TabNavigatorComponent implements OnInit { 19 | @Input() page = 0; 20 | @Input() pageCount = 0; 21 | @Output() pageChange = new EventEmitter(); 22 | 23 | service = inject(TabbedPaneService); 24 | 25 | ngOnInit(): void { 26 | this.service.pageCount.subscribe((pageCount) => { 27 | this.pageCount = pageCount; 28 | }); 29 | this.service.currentPage.subscribe((page) => { 30 | this.page = page; 31 | }); 32 | } 33 | 34 | prev(): void { 35 | this.page--; 36 | if (this.page < 0) { 37 | this.page = this.pageCount - 1; 38 | } 39 | this.pageChange.emit(this.page); 40 | this.service.currentPage.next(this.page); 41 | } 42 | 43 | next(): void { 44 | this.page++; 45 | if (this.page >= this.pageCount) { 46 | this.page = 0; 47 | } 48 | this.pageChange.emit(this.page); 49 | this.service.currentPage.next(this.page); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab/tab.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/ui-common/tab/tab.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab/tab.component.html: -------------------------------------------------------------------------------- 1 | @if(visible) { 2 |
3 |

{{ title }}

4 | 5 |
6 | } 7 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tab/tab.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'app-tab', 6 | imports: [CommonModule], 7 | templateUrl: './tab.component.html', 8 | styleUrls: ['./tab.component.css'] 9 | }) 10 | export class TabComponent { 11 | @Input() title = ''; 12 | visible = true; 13 | } 14 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.css: -------------------------------------------------------------------------------- 1 | .navigation { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .tab-link { 6 | font-size: 16px; 7 | padding-bottom: 3px; 8 | border-bottom: 5px solid darkseagreen; 9 | margin-right: 10px; 10 | } 11 | 12 | .tab-link a { 13 | color: black; 14 | cursor: pointer; 15 | } 16 | 17 | .tab-link a:hover { 18 | color: orangered; 19 | text-decoration: none; 20 | } 21 | 22 | .tab-link a.active { 23 | color: orangered; 24 | } 25 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.html: -------------------------------------------------------------------------------- 1 |
2 | 12 | 13 | 14 | 15 | 16 |

 

17 |
18 | 19 | {{ navigator.page + 1 }} 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AfterContentInit, 3 | AfterViewInit, 4 | ChangeDetectorRef, 5 | Component, 6 | ContentChildren, 7 | inject, 8 | QueryList, 9 | ViewChild, 10 | } from '@angular/core'; 11 | import { CommonModule } from '@angular/common'; 12 | import { TabComponent } from '../tab/tab.component'; 13 | import { TabNavigatorComponent } from '../tab-navigator/tab-navigator.component'; 14 | import { TabbedPaneService } from './tabbed-pane.service'; 15 | 16 | @Component({ 17 | selector: 'app-tabbed-pane', 18 | templateUrl: './tabbed-pane.component.html', 19 | styleUrls: ['./tabbed-pane.component.css'], 20 | imports: [CommonModule, TabNavigatorComponent], 21 | providers: [TabbedPaneService] 22 | }) 23 | export class TabbedPaneComponent implements AfterContentInit, AfterViewInit { 24 | @ContentChildren(TabComponent) 25 | tabQueryList: QueryList | undefined; 26 | 27 | @ViewChild('navigator') 28 | navigator: TabNavigatorComponent | undefined; 29 | 30 | activeTab: TabComponent | undefined; 31 | 32 | currentPage = 0; 33 | 34 | cd = inject(ChangeDetectorRef); 35 | service = inject(TabbedPaneService); 36 | 37 | get tabs(): TabComponent[] { 38 | return this.tabQueryList?.toArray() ?? []; 39 | } 40 | 41 | ngAfterContentInit(): void { 42 | if (this.tabs.length > 0) { 43 | this.activate(this.tabs[0]); 44 | } 45 | } 46 | 47 | ngAfterViewInit(): void { 48 | this.service.pageCount.next(this.tabs.length); 49 | 50 | // Use this strategy with caution 51 | this.cd.detectChanges(); 52 | 53 | this.service.currentPage.subscribe((page: number) => { 54 | // Prevent cycle: 55 | if (page === this.currentPage) { 56 | return; 57 | } 58 | this.pageChange(page); 59 | }); 60 | } 61 | 62 | register(tab: TabComponent): void { 63 | this.tabs.push(tab); 64 | } 65 | 66 | activate(active: TabComponent): void { 67 | for (const tab of this.tabs) { 68 | tab.visible = tab === active; 69 | } 70 | this.activeTab = active; 71 | 72 | this.currentPage = this.tabs.indexOf(active); 73 | this.service.currentPage.next(this.currentPage); 74 | } 75 | 76 | pageChange(page: number): void { 77 | this.activate(this.tabs[page]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tabbed-pane/tabbed-pane.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs'; 3 | 4 | @Injectable({ 5 | providedIn: 'root', 6 | }) 7 | export class TabbedPaneService { 8 | readonly pageCount = new BehaviorSubject(0); 9 | readonly currentPage = new BehaviorSubject(1); 10 | } 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/ui-common/tooltip.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | EmbeddedViewRef, 4 | inject, 5 | Input, 6 | TemplateRef, 7 | ViewContainerRef, 8 | HostListener, 9 | OnInit, 10 | } from '@angular/core'; 11 | 12 | // Context Information to be passed to the template 13 | type TipToolContext = { 14 | $implicit: string; 15 | text: string; 16 | }; 17 | 18 | @Directive({ 19 | selector: '[appTooltip]', 20 | standalone: true, 21 | }) 22 | export class TooltipDirective implements OnInit { 23 | viewContainer = inject(ViewContainerRef); 24 | viewRef: EmbeddedViewRef | undefined; 25 | 26 | @Input('appTooltip') template: TemplateRef | undefined; 27 | 28 | setHidden(hidden: boolean): void { 29 | this.viewRef?.rootNodes.forEach((nativeElement) => { 30 | nativeElement.hidden = hidden; 31 | }); 32 | } 33 | 34 | ngOnInit(): void { 35 | if (!this.template) { 36 | return; 37 | } 38 | this.viewRef = this.viewContainer.createEmbeddedView(this.template, { 39 | $implicit: 'Tooltip!', 40 | text: 'Important Information!', 41 | }); 42 | 43 | this.setHidden(true); 44 | } 45 | 46 | @HostListener('mouseover') 47 | mouseover() { 48 | this.setHidden(false); 49 | } 50 | 51 | @HostListener('mouseout') 52 | mouseout() { 53 | this.setHidden(true); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root', 5 | }) 6 | export class AuthService { 7 | private _userName = ''; 8 | 9 | get userName(): string { 10 | return this._userName; 11 | } 12 | 13 | login(userName: string): void { 14 | // Auth for *very honest* users TM 15 | this._userName = userName; 16 | } 17 | 18 | logout(): void { 19 | this._userName = ''; 20 | } 21 | 22 | isAuth(): boolean { 23 | return this._userName !== ''; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-auth/auth.store.ts: -------------------------------------------------------------------------------- 1 | import { signalStore, withState } from "@ngrx/signals"; 2 | import { withDevtools } from '@angular-architects/ngrx-toolkit'; 3 | 4 | export const AuthStore = signalStore( 5 | { providedIn: 'root', protectedState: false }, 6 | withState({ 7 | userId: 2 8 | }), 9 | withDevtools('auth'), 10 | // withComputed 11 | // withMethods 12 | ); 13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-auth/auth.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { Router, UrlTree } from '@angular/router'; 3 | import { AuthService } from './auth.service'; 4 | 5 | export function checkAuth(): boolean | UrlTree { 6 | const auth = inject(AuthService); 7 | const router = inject(Router); 8 | 9 | if (!auth.isAuth()) { 10 | return router.createUrlTree(['/home', { login: true }]); 11 | } 12 | 13 | return true; 14 | } 15 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-auth/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './auth.service'; 6 | export * from './auth'; 7 | export * from './auth.store'; -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-common/can-exit.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | export interface CanExit { 4 | canExit(): Observable; 5 | } 6 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './can-exit'; 2 | export * from './with-data-service.feature'; 3 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-common/with-data-service.feature.ts: -------------------------------------------------------------------------------- 1 | import { ProviderToken, inject } from "@angular/core"; 2 | import { patchState, signalStoreFeature, withMethods, withState } from "@ngrx/signals"; 3 | import { rxMethod } from "@ngrx/signals/rxjs-interop"; 4 | import { pipe, tap, switchMap, catchError, of, Observable } from "rxjs"; 5 | 6 | export type CallState = 'init' | 'loading' | 'loaded' | { error: unknown }; 7 | 8 | export interface Entity { 9 | id: string | number 10 | } 11 | 12 | export type Filter = Record; 13 | 14 | export interface DataService { 15 | load(filter: F): Observable; 16 | } 17 | 18 | export function withDataService(options: { dataServiceToken: ProviderToken>; filter: F }) { 19 | return signalStoreFeature( 20 | withState({ 21 | state: 'init' as CallState, 22 | filter: options.filter, 23 | entities: [] as E[], 24 | selectedIds: {} as Record 25 | }), 26 | withMethods(( 27 | store, 28 | dataService = inject(options.dataServiceToken) 29 | ) => ({ 30 | updateFilter(filter: F) { 31 | patchState(store, { filter }); 32 | }, 33 | updateSelected(id: number, selected: boolean): void { 34 | patchState(store, (store) => ({ 35 | selectedIds: { 36 | ...store.selectedIds, 37 | [id]: selected 38 | } 39 | })); 40 | }, 41 | load: rxMethod(pipe( 42 | tap(() => patchState(store, { state: 'loading' })), 43 | switchMap(() => dataService.load(store.filter()).pipe( 44 | tap((entities) => patchState(store, { entities, state: 'loaded' })), 45 | catchError((error) => { 46 | patchState(store, { state: { error } }); 47 | return of([]); 48 | }) 49 | )) 50 | )) 51 | })) 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-config/config.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { inject, Injectable } from '@angular/core'; 3 | import { BehaviorSubject, filter } from 'rxjs'; 4 | import { Config, initConfig } from './config'; 5 | 6 | @Injectable({ 7 | providedIn: 'root', 8 | }) 9 | export class ConfigService { 10 | private http = inject(HttpClient); 11 | private _config = initConfig; 12 | 13 | private loadedSubject = new BehaviorSubject(false); 14 | readonly loaded$ = this.loadedSubject 15 | .asObservable() 16 | .pipe(filter((value) => !!value)); 17 | 18 | get config(): Config { 19 | return { ...this._config }; 20 | } 21 | 22 | loadConfig() { 23 | this.http.get('./assets/config.json').subscribe((config) => { 24 | this._config = config; 25 | this.loadedSubject.next(true); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-config/config.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | baseUrl: string; 3 | } 4 | 5 | export const initConfig: Config = { 6 | baseUrl: '', 7 | }; 8 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-config/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './config.service'; 6 | export * from './config'; 7 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/color-config.ts: -------------------------------------------------------------------------------- 1 | export abstract class ColorConfig { 2 | abstract debug: number; 3 | abstract info: number; 4 | abstract error: number; 5 | } 6 | 7 | // Color Code from https://en.m.wikipedia.org/wiki/ANSI_escape_code#Colors 8 | export const defaultColorConfig: ColorConfig = { 9 | debug: 32, 10 | info: 34, 11 | error: 31, 12 | }; 13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/color.service.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable } from '@angular/core'; 2 | import { ColorConfig } from './color-config'; 3 | import { LogLevel } from './log-level'; 4 | 5 | export abstract class ColorService { 6 | abstract apply(level: LogLevel, msg: string): string; 7 | } 8 | 9 | @Injectable() 10 | export class DefaultColorService implements ColorService { 11 | config = inject(ColorConfig); 12 | 13 | apply(level: LogLevel, msg: string): string { 14 | const key = LogLevel[level].toLowerCase() as keyof ColorConfig; 15 | const color = this.config[key]; 16 | 17 | // For the sake of simplicity, we don't use an external 18 | // library like chalk here. Instead, we just send 19 | // commands to the console for controlling the color 20 | return `\x1b[${color}m${msg}\x1b[0m`; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/color.ts: -------------------------------------------------------------------------------- 1 | import { ColorConfig, defaultColorConfig } from './color-config'; 2 | import { ColorService, DefaultColorService } from './color.service'; 3 | import { LoggerFeature, LoggerFeatureKind } from './features'; 4 | 5 | export function withColor(config?: Partial): LoggerFeature { 6 | const internal = { ...defaultColorConfig, ...config }; 7 | 8 | return { 9 | kind: LoggerFeatureKind.COLOR, 10 | providers: [ 11 | { 12 | provide: ColorConfig, 13 | useValue: internal, 14 | }, 15 | { 16 | provide: ColorService, 17 | useClass: DefaultColorService, 18 | }, 19 | ], 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/custom-log-appender.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { LogAppender } from './log-appender'; 3 | import { LogLevel } from './log-level'; 4 | 5 | @Injectable() 6 | export class CustomLogAppender implements LogAppender { 7 | logs: string[] = []; 8 | 9 | append(level: LogLevel, category: string, msg: string): void { 10 | this.logs.push(msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/features.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@angular/core'; 2 | 3 | export enum LoggerFeatureKind { 4 | COLOR, 5 | OTHER_FEATURE, 6 | ADDITIONAL_FEATURE, 7 | } 8 | 9 | export interface LoggerFeature { 10 | kind: LoggerFeatureKind; 11 | providers: Provider[]; 12 | } 13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './color-config'; 6 | export * from './color.service'; 7 | export * from './color'; 8 | export * from './custom-log-appender'; 9 | export * from './features'; 10 | export * from './log-appender'; 11 | export * from './log-formatter'; 12 | export * from './log-level'; 13 | export * from './logger-config'; 14 | export * from './logger'; 15 | export * from './provider'; 16 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/log-appender.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable, InjectionToken } from '@angular/core'; 2 | import { ColorService } from './color.service'; 3 | import { LogLevel } from './log-level'; 4 | 5 | export abstract class LogAppender { 6 | abstract append(level: LogLevel, category: string, msg: string): void; 7 | } 8 | 9 | @Injectable() 10 | export class DefaultLogAppender implements LogAppender { 11 | colorService = inject(ColorService, { optional: true, self: true }); 12 | 13 | append(level: LogLevel, category: string, msg: string): void { 14 | if (this.colorService) { 15 | msg = this.colorService.apply(level, msg); 16 | } 17 | console.log(msg); 18 | } 19 | } 20 | 21 | export const LOG_APPENDERS = new InjectionToken('LOG_APPENDERS'); 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/log-formatter.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { LogLevel } from './log-level'; 3 | 4 | export const LOG_FORMATTER = new InjectionToken('LOG_FORMATTER'); 5 | 6 | export type LogFormatFn = ( 7 | level: LogLevel, 8 | category: string, 9 | msg: string 10 | ) => string; 11 | 12 | export const defaultLogFormatFn: LogFormatFn = (level, category, msg) => { 13 | const levelString = LogLevel[level].padEnd(5); 14 | return `[${levelString}] ${category.toUpperCase()} ${msg}`; 15 | }; 16 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/log-level.ts: -------------------------------------------------------------------------------- 1 | export enum LogLevel { 2 | DEBUG = 0, 3 | INFO = 1, 4 | ERROR = 2, 5 | } 6 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/logger-config.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { DefaultLogAppender, LogAppender } from './log-appender'; 3 | import { defaultLogFormatFn, LogFormatFn } from './log-formatter'; 4 | import { LogLevel } from './log-level'; 5 | 6 | export abstract class LoggerConfig { 7 | abstract level: LogLevel; 8 | abstract formatter: LogFormatFn; 9 | abstract appenders: Type[]; 10 | } 11 | 12 | export const defaultConfig: LoggerConfig = { 13 | level: LogLevel.DEBUG, 14 | formatter: defaultLogFormatFn, 15 | appenders: [DefaultLogAppender], 16 | }; 17 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/logger.ts: -------------------------------------------------------------------------------- 1 | import { inject, Injectable } from '@angular/core'; 2 | import { LOG_APPENDERS } from './log-appender'; 3 | import { LOG_FORMATTER } from './log-formatter'; 4 | import { LogLevel } from './log-level'; 5 | import { LoggerConfig } from './logger-config'; 6 | 7 | @Injectable() 8 | export class LoggerService { 9 | private formatter = inject(LOG_FORMATTER); 10 | private config = inject(LoggerConfig); 11 | private appenders = inject(LOG_APPENDERS); 12 | 13 | private parentLogger = inject(LoggerService, { 14 | optional: true, 15 | skipSelf: true, 16 | }); 17 | 18 | log(level: LogLevel, category: string, msg: string): void { 19 | if (level < this.config.level) { 20 | return; 21 | } 22 | 23 | const formatted = this.formatter(level, category, msg); 24 | 25 | for (const a of this.appenders) { 26 | a.append(level, category, formatted); 27 | } 28 | 29 | if (this.parentLogger) { 30 | this.parentLogger.log(level, category, msg); 31 | } 32 | } 33 | 34 | error(category: string, msg: string): void { 35 | this.log(LogLevel.ERROR, category, msg); 36 | } 37 | 38 | info(category: string, msg: string): void { 39 | this.log(LogLevel.INFO, category, msg); 40 | } 41 | 42 | debug(category: string, msg: string): void { 43 | this.log(LogLevel.DEBUG, category, msg); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-logger/provider.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core'; 2 | import { LoggerFeature, LoggerFeatureKind } from './features'; 3 | import { LOG_APPENDERS } from './log-appender'; 4 | import { LOG_FORMATTER } from './log-formatter'; 5 | import { LoggerService } from './logger'; 6 | import { defaultConfig, LoggerConfig } from './logger-config'; 7 | 8 | export function provideLogger( 9 | config: Partial, 10 | ...features: LoggerFeature[] 11 | ): EnvironmentProviders { 12 | const merged = { ...defaultConfig, ...config }; 13 | 14 | const colorFeatures = 15 | features?.filter((f) => f.kind === LoggerFeatureKind.COLOR)?.length ?? 0; 16 | 17 | if (colorFeatures > 1) { 18 | throw new Error('Only one color feature allowed for logger!'); 19 | } 20 | 21 | return makeEnvironmentProviders([ 22 | LoggerService, 23 | { 24 | provide: LoggerConfig, 25 | useValue: merged, 26 | }, 27 | { 28 | provide: LOG_FORMATTER, 29 | useValue: merged.formatter, 30 | }, 31 | merged.appenders.map((a) => ({ 32 | provide: LOG_APPENDERS, 33 | useClass: a, 34 | multi: true, 35 | })), 36 | features.map((f) => f.providers), 37 | ]); 38 | } 39 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Automatically generated by barrelsby. 3 | */ 4 | 5 | export * from './validation/city-validator.directive'; 6 | export * from './validation/city-validator'; 7 | export * from './validation/roundtrip-validator.directive'; 8 | export * from './validation/roundtrip-validator'; 9 | export * from './validation-errors/validation-errors.component'; 10 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.html: -------------------------------------------------------------------------------- 1 | @if (hasErrors) { 2 |
Internal error object: {{ errors | json }}
3 | } 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation-errors/validation-errors.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ValidationErrors } from '@angular/forms'; 4 | 5 | export type Error = Record; 6 | 7 | @Component({ 8 | selector: 'app-validation-errors', 9 | standalone: true, 10 | imports: [CommonModule], 11 | templateUrl: './validation-errors.component.html', 12 | styleUrls: ['./validation-errors.component.css'], 13 | }) 14 | export class ValidationErrorsComponent implements OnChanges { 15 | @Input() errors: ValidationErrors = {}; 16 | @Input() field = ''; 17 | hasErrors = false; 18 | 19 | ngOnChanges(changes: SimpleChanges): void { 20 | this.hasErrors = Object.keys(changes['errors'].currentValue).length > 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation/city-validator.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input } from '@angular/core'; 2 | import { 3 | AbstractControl, 4 | NG_VALIDATORS, 5 | ValidationErrors, 6 | Validator, 7 | } from '@angular/forms'; 8 | 9 | @Directive({ 10 | selector: '[appCity]', 11 | standalone: true, 12 | providers: [ 13 | { 14 | provide: NG_VALIDATORS, 15 | useExisting: CityValidatorDirective, 16 | multi: true, 17 | }, 18 | ], 19 | }) 20 | export class CityValidatorDirective implements Validator { 21 | @Input('appCity') validCities: string[] = []; 22 | 23 | validate(c: AbstractControl): ValidationErrors | null { 24 | if (c.value && this.validCities.indexOf(c.value) === -1) { 25 | return { 26 | city: { 27 | actualValue: c.value, 28 | validCities: this.validCities, 29 | }, 30 | }; 31 | } 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation/city-validator.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, ValidatorFn } from '@angular/forms'; 2 | 3 | export function validateCity(validCities: string[]): ValidatorFn { 4 | return (c: AbstractControl) => { 5 | if (c.value && validCities.indexOf(c.value) === -1) { 6 | return { 7 | city: { 8 | actualValue: c.value, 9 | validCities: validCities, 10 | }, 11 | }; 12 | } 13 | return null; 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation/roundtrip-validator.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive } from '@angular/core'; 2 | import { 3 | AbstractControl, 4 | FormGroup, 5 | NG_VALIDATORS, 6 | ValidationErrors, 7 | Validator, 8 | } from '@angular/forms'; 9 | 10 | @Directive({ 11 | selector: '[appRoundTrip]', 12 | standalone: true, 13 | providers: [ 14 | { 15 | provide: NG_VALIDATORS, 16 | useExisting: RoundtripValidatorDirective, 17 | multi: true, 18 | }, 19 | ], 20 | }) 21 | export class RoundtripValidatorDirective implements Validator { 22 | validate(control: AbstractControl): ValidationErrors | null { 23 | const group: FormGroup = control as FormGroup; // Typumwandlung 24 | 25 | const fromCtrl = group.controls['from']; 26 | const toCtrl = group.controls['to']; 27 | 28 | if (!fromCtrl || !toCtrl) { 29 | return null; 30 | } 31 | 32 | if (fromCtrl.value === toCtrl.value) { 33 | return { 34 | roundTrip: true, 35 | }; 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/shared/util-validation/validation/roundtrip-validator.ts: -------------------------------------------------------------------------------- 1 | import { AbstractControl, FormGroup, ValidationErrors } from '@angular/forms'; 2 | 3 | export function validateRoundTrip( 4 | control: AbstractControl 5 | ): ValidationErrors | null { 6 | const group: FormGroup = control as FormGroup; 7 | 8 | const fromCtrl = group.controls['from']; 9 | const toCtrl = group.controls['to']; 10 | 11 | if (!fromCtrl || !toCtrl) { 12 | return null; 13 | } 14 | 15 | if (fromCtrl.value === toCtrl.value) { 16 | return { 17 | roundTrip: true, 18 | }; 19 | } 20 | 21 | return null; 22 | } 23 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/flight-filter.ts: -------------------------------------------------------------------------------- 1 | export type FlightFilter = { 2 | from: string; 3 | to: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/flight.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { inject, Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Flight } from './flight'; 5 | import { ConfigService } from '@demo/shared/util-config'; 6 | import { FlightFilter } from './flight-filter'; 7 | import { DataService } from '@demo/shared/util-common'; 8 | 9 | @Injectable({ 10 | providedIn: 'root', 11 | }) 12 | export class FlightService implements DataService { 13 | private http = inject(HttpClient); 14 | private configService = inject(ConfigService); 15 | 16 | load(filter: FlightFilter): Observable { 17 | const url = `${this.configService.config.baseUrl}/flight`; 18 | 19 | const headers = { 20 | Accept: 'application/json', 21 | }; 22 | 23 | const params = filter; 24 | 25 | return this.http.get(url, { headers, params }); 26 | } 27 | 28 | loadById(id: string): Observable { 29 | const url = `${this.configService.config.baseUrl}/flight`; 30 | 31 | const headers = { 32 | Accept: 'application/json', 33 | }; 34 | 35 | const params = { id }; 36 | 37 | return this.http.get(url, { headers, params }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/flight.ts: -------------------------------------------------------------------------------- 1 | export interface Flight { 2 | id: number; 3 | from: string; 4 | to: string; 5 | date: string; 6 | delayed: boolean; 7 | } 8 | 9 | export const initFlight: Flight = { 10 | id: 0, 11 | from: '', 12 | to: '', 13 | date: '', 14 | delayed: false, 15 | }; 16 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flight.service'; 2 | export * from './flight'; 3 | export * from './flight-filter'; 4 | export * from './ticket.service'; 5 | export * from './ticket-filter'; 6 | export * from './ticket'; -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/ticket-filter.ts: -------------------------------------------------------------------------------- 1 | export type TicketFilter = { 2 | passengerId: number; 3 | } 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/ticket.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { inject, Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { ConfigService } from '@demo/shared/util-config'; 5 | import { Ticket } from './ticket'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class TicketService { 11 | private http = inject(HttpClient); 12 | private configService = inject(ConfigService); 13 | 14 | load(passengerId: number): Observable { 15 | const url = `${this.configService.config.baseUrl}/booking`; 16 | 17 | const headers = { 18 | Accept: 'application/json', 19 | }; 20 | 21 | const params = { passengerId, expand: true }; 22 | 23 | return this.http.get(url, { headers, params }); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/data/ticket.ts: -------------------------------------------------------------------------------- 1 | import { Flight } from "./flight" 2 | 3 | export interface Ticket { 4 | id: number 5 | passengerId: number 6 | flightId: number 7 | bookingDate: string 8 | flightClass: number 9 | seat: string 10 | flight: Flight 11 | } 12 | 13 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/booking.store.ts: -------------------------------------------------------------------------------- 1 | import { signalStore } from '@ngrx/signals'; 2 | import { FlightService } from '../data'; 3 | import { withDataService } from '@demo/shared/util-common'; 4 | import { withDevtools } from '@angular-architects/ngrx-toolkit'; 5 | 6 | export const BookingStore = signalStore( 7 | { providedIn: 'root', protectedState: false }, 8 | withDataService({ 9 | dataServiceToken: FlightService, 10 | filter: { from: 'London', to: 'Paris' } 11 | }), 12 | withDevtools('booking'), 13 | ); 14 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterLink, RouterOutlet } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-flight-booking', 7 | imports: [CommonModule, RouterLink, RouterOutlet], 8 | templateUrl: './flight-booking.component.html', 9 | styleUrls: ['./flight-booking.component.css'] 10 | }) 11 | export class FlightBookingComponent {} 12 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-booking.routes.ts: -------------------------------------------------------------------------------- 1 | import { inject } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, Routes } from '@angular/router'; 3 | import { FlightBookingComponent } from './flight-booking.component'; 4 | import { FlightEditComponent } from './flight-edit/flight-edit.component'; 5 | import { FlightSearchComponent } from './flight-search/flight-search.component'; 6 | import { PassengerSearchComponent } from './passenger-search/passenger-search.component'; 7 | import { FlightService } from '@demo/ticketing/data'; 8 | import { checkAuth } from '@demo/shared/util-auth'; 9 | 10 | export const FLIGHT_BOOKING_ROUTES: Routes = [ 11 | { 12 | path: '', 13 | component: FlightBookingComponent, 14 | providers: [], 15 | children: [ 16 | { 17 | path: '', 18 | redirectTo: 'flight-search', 19 | pathMatch: 'full', 20 | }, 21 | { 22 | path: 'flight-search', 23 | component: FlightSearchComponent, 24 | }, 25 | { 26 | path: 'flight-edit/:id', 27 | component: FlightEditComponent, 28 | resolve: { 29 | flight: (r: ActivatedRouteSnapshot) => 30 | inject(FlightService).loadById(r.params['id']), 31 | }, 32 | }, 33 | { 34 | path: 'passenger-search', 35 | component: PassengerSearchComponent, 36 | canActivate: [checkAuth], 37 | }, 38 | ], 39 | }, 40 | ]; 41 | 42 | export default FLIGHT_BOOKING_ROUTES; 43 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Flight Edit (Reactive)

3 | 4 | 8 | 9 | 13 | 14 | @if (form.controls['from'].pending) { 15 |
Validating ...
16 | } 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 28 | 29 |
30 |
31 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit-reactive/flight-edit-reactive.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; 4 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 5 | import { 6 | ValidationErrorsComponent, 7 | validateCity, 8 | validateRoundTrip, 9 | } from '@demo/shared/util-validation'; 10 | import { Flight, FlightService } from '@demo/ticketing/data'; 11 | 12 | @Component({ 13 | selector: 'app-flight-edit-reactive', 14 | standalone: true, 15 | templateUrl: './flight-edit-reactive.component.html', 16 | styleUrls: ['./flight-edit-reactive.component.css'], 17 | imports: [CommonModule, ReactiveFormsModule, ValidationErrorsComponent], 18 | }) 19 | export class FlightEditReactiveComponent { 20 | private flightService = inject(FlightService); 21 | 22 | private dialogRef = inject(MatDialogRef); 23 | private data = inject<{ flight: Flight }>(MAT_DIALOG_DATA); 24 | flight = this.data.flight; 25 | 26 | private fb = inject(FormBuilder); 27 | 28 | form = this.fb.nonNullable.group({ 29 | id: [0], 30 | from: [ 31 | '', 32 | [ 33 | Validators.required, 34 | Validators.minLength(3), 35 | validateCity(['London', 'Paris', 'Berlin']), 36 | ], 37 | ], 38 | to: [''], 39 | date: [''], 40 | delayed: [false], 41 | }); 42 | 43 | constructor() { 44 | this.form.addValidators(validateRoundTrip); 45 | 46 | this.form.patchValue(this.flight); 47 | 48 | this.form.valueChanges.subscribe((flightForm) => { 49 | console.log('flight form changed:', flightForm); 50 | }); 51 | 52 | this.form.controls.from.valueChanges.subscribe((from) => { 53 | console.log('from changed:', from); 54 | }); 55 | } 56 | 57 | save(): void { 58 | this.flight = this.form.getRawValue(); 59 | console.log('flight', this.flight); 60 | } 61 | 62 | close(): void { 63 | this.dialogRef.close(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.html: -------------------------------------------------------------------------------- 1 | @if (flight.id !== 0) { 2 |
3 |

Flight Edit

4 | 5 |
ShowDetails: {{ showDetails }}
6 | 7 |
8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 22 | 23 |
24 |
25 |
26 | } 27 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-edit/flight-edit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject, OnInit } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { ActivatedRoute } from '@angular/router'; 6 | import { Observable } from 'rxjs'; 7 | import { Dialog } from '@angular/cdk/dialog'; 8 | import { ConfirmComponent, DateCvaDirective } from '@demo/shared/ui-common'; 9 | import { initFlight } from '@demo/ticketing/data'; 10 | import { CanExit } from '@demo/shared/util-common'; 11 | 12 | @Component({ 13 | selector: 'app-flight-edit', 14 | imports: [CommonModule, FormsModule, DateCvaDirective], 15 | templateUrl: './flight-edit.component.html', 16 | styleUrls: ['./flight-edit.component.css'] 17 | }) 18 | export class FlightEditComponent implements OnInit, CanExit { 19 | private route = inject(ActivatedRoute); 20 | private dialog = inject(Dialog); 21 | 22 | id = ''; 23 | showDetails = ''; 24 | flight = initFlight; 25 | 26 | ngOnInit(): void { 27 | this.route.paramMap.subscribe((params) => { 28 | this.id = params.get('id') ?? ''; 29 | this.showDetails = params.get('showDetails') ?? ''; 30 | }); 31 | 32 | this.route.data.subscribe((data) => { 33 | this.flight = data['flight']; 34 | }); 35 | } 36 | 37 | canExit(): Observable { 38 | const confirm = this.dialog.open(ConfirmComponent, { 39 | data: 'Do you really want to leave me?', 40 | }); 41 | return confirm.closed as Observable; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Flight Search

3 |
4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 | 20 |
21 | @for (f of flights(); track f.id) { 22 |
23 | 28 | 29 |
30 | } 31 |
32 | 33 |

 

34 | 35 |
36 |
{{ basket() | json }}
37 |
38 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/flight-search/flight-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject, linkedSignal, signal } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { FlightCardComponent } from '@demo/shared/ui-common'; 5 | import { BookingStore } from '../booking.store'; 6 | 7 | // import { CheckinService } from '@demo/checkin/data'; 8 | 9 | @Component({ 10 | selector: 'app-flight-search', 11 | templateUrl: './flight-search.component.html', 12 | styleUrls: ['./flight-search.component.css'], 13 | imports: [CommonModule, FormsModule, FlightCardComponent] 14 | }) 15 | export class FlightSearchComponent { 16 | private store = inject(BookingStore); 17 | 18 | from = linkedSignal(() => this.store.filter.from()); 19 | to = linkedSignal(() => this.store.filter.to()); 20 | flights = this.store.entities; 21 | 22 | basket = signal>({ 23 | 3: true, 24 | 5: true, 25 | }); 26 | 27 | search(): void { 28 | this.store.updateFilter({ 29 | from: this.from(), 30 | to: this.to() 31 | }); 32 | this.store.load(); 33 | } 34 | 35 | updateBasket(flightId: number, selected: boolean): void { 36 | this.store.updateSelected(flightId, selected); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flight-booking.routes'; 2 | 3 | // eslint-disable-next-line 4 | export * from '../../shared/ui-common/flight-card/flight-card.component'; -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.html: -------------------------------------------------------------------------------- 1 |

passenger-search works!

2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-booking/passenger-search/passenger-search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoggerService } from '@demo/shared/util-logger'; 4 | 5 | @Component({ 6 | selector: 'app-passenger-search', 7 | imports: [CommonModule], 8 | templateUrl: './passenger-search.component.html', 9 | styleUrls: ['./passenger-search.component.css'] 10 | }) 11 | export class PassengerSearchComponent { 12 | constructor(logger: LoggerService) { 13 | logger.info('passenger search', 'info'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.html: -------------------------------------------------------------------------------- 1 | Check in for your flights now! 2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/checkin/checkin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-checkin', 5 | templateUrl: './checkin.component.html', 6 | styleUrls: ['./checkin.component.css'], 7 | standalone: false 8 | }) 9 | export class CheckinComponent {} 10 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/index.ts: -------------------------------------------------------------------------------- 1 | export * from './next-flights.module'; 2 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.css: -------------------------------------------------------------------------------- 1 | .flight { 2 | margin-bottom: 20px; 3 | } -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.html: -------------------------------------------------------------------------------- 1 |

Your Next Flights

2 | 3 | @for(f of flights(); track f.id) { 4 |
5 | 6 |
7 | } -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { TicketStore } from './tickets.store'; 3 | 4 | @Component({ 5 | selector: 'app-next-flights', 6 | templateUrl: './next-flights.component.html', 7 | styleUrls: ['./next-flights.component.css'], 8 | standalone: false 9 | }) 10 | export class NextFlightsComponent { 11 | store = inject(TicketStore); 12 | 13 | flights = this.store.flights; 14 | 15 | constructor() { 16 | this.store.loadByPassenger(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NextFlightsComponent } from './next-flights.component'; 4 | import { CheckinComponent } from './checkin/checkin.component'; 5 | import { NextFlightsService } from './next-flights.service'; 6 | import { RouterModule } from '@angular/router'; 7 | import { NEXT_FLIGHTS_ROUTES } from './next-flights.routes'; 8 | import { FlightCardComponent } from '@demo/shared/ui-common'; 9 | 10 | @NgModule({ 11 | declarations: [NextFlightsComponent, CheckinComponent], 12 | providers: [NextFlightsService], 13 | exports: [NextFlightsComponent], 14 | imports: [CommonModule, RouterModule.forChild(NEXT_FLIGHTS_ROUTES), FlightCardComponent] 15 | }) 16 | export class NextFlightsModule {} 17 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { NextFlightsComponent } from './next-flights.component'; 3 | 4 | export const NEXT_FLIGHTS_ROUTES: Routes = [ 5 | { 6 | path: '', 7 | pathMatch: 'full', 8 | component: NextFlightsComponent, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/next-flights.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, computed, inject } from '@angular/core'; 2 | import { AuthStore } from '@demo/shared/util-auth'; 3 | import { TicketStore } from './tickets.store'; 4 | 5 | @Injectable() 6 | export class NextFlightsService { 7 | private authStore = inject(AuthStore); 8 | private ticketStore = inject(TicketStore); 9 | 10 | readonly tickets = this.ticketStore.tickets; 11 | readonly flights = computed(() => this.tickets().map(t => t.flight)); 12 | 13 | load(): void { 14 | this.ticketStore.loadByPassenger(this.authStore.userId()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/flights/src/app/domains/ticketing/feature-next-flights/tickets.store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | patchState, 3 | signalStore, 4 | withComputed, 5 | withMethods, 6 | withState, 7 | } from '@ngrx/signals'; 8 | import { Ticket, TicketService } from '../data'; 9 | import { computed, inject } from '@angular/core'; 10 | import { withDevtools } from '@angular-architects/ngrx-toolkit'; 11 | import { AuthStore } from '@demo/shared/util-auth'; 12 | 13 | export const TicketStore = signalStore( 14 | { providedIn: 'root', protectedState: false }, 15 | withState({ 16 | tickets: [] as Ticket[], 17 | }), 18 | withComputed((store) => ({ 19 | flights: computed(() => store.tickets().map((t) => t.flight)), 20 | })), 21 | withMethods( 22 | ( 23 | store, 24 | ticketService = inject(TicketService), 25 | authStore = inject(AuthStore) 26 | ) => ({ 27 | loadByPassenger(passengerId?: number) { 28 | if (typeof passengerId == 'undefined') { 29 | passengerId = authStore.userId(); 30 | } 31 | 32 | ticketService.load(passengerId).subscribe((tickets) => { 33 | patchState(store, { tickets }); 34 | }); 35 | }, 36 | }) 37 | ), 38 | withDevtools('ticket') 39 | ); 40 | 41 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/about/about.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/shell/about/about.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/shell/about/about.component.html: -------------------------------------------------------------------------------- 1 |

About

2 | 3 | Fligh-App by AngularArchitects.io 4 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/about/about.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'app-about', 6 | templateUrl: './about.component.html', 7 | styleUrls: ['./about.component.css'], 8 | imports: [CommonModule] 9 | }) 10 | export class AboutComponent {} 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/basket/basket.component.css: -------------------------------------------------------------------------------- 1 | .gray-bg { 2 | opacity: 0.4; 3 | background-color: black; 4 | left: 0px; 5 | top: 0px; 6 | width: 100%; 7 | height: 100%; 8 | position: fixed; 9 | z-index: 2000; 10 | } 11 | 12 | .flight-history { 13 | left: 0px; 14 | top: 0px; 15 | width: 100%; 16 | height: 100%; 17 | position: fixed; 18 | z-index: 2010; 19 | } 20 | 21 | .flight-history-inside { 22 | background-color: white; 23 | margin: 100px auto; 24 | width: 400px; 25 | } 26 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/basket/basket.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |

Your Basket

7 | 8 |
    9 |
  • 10 |

    Hamburg - Graz

    11 |
  • 12 |
  • 13 |

    Graz - Hamburg

    14 |
  • 15 |
16 | 17 | 21 | Close 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/basket/basket.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterLink } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-basket', 7 | imports: [CommonModule, RouterLink], 8 | templateUrl: './basket.component.html', 9 | styleUrls: ['./basket.component.css'] 10 | }) 11 | export class BasketComponent {} 12 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/shell/home/home.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/shell/home/home.component.html: -------------------------------------------------------------------------------- 1 | @if (!auth.userName) { 2 |

Welcome!

3 | } @else { 4 |

Welcome {{ auth.userName }}!

5 | } 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, inject } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoggerService } from '@demo/shared/util-logger'; 4 | import { AuthService } from '@demo/shared/util-auth'; 5 | 6 | @Component({ 7 | selector: 'app-home', 8 | imports: [CommonModule], 9 | templateUrl: './home.component.html', 10 | styleUrls: ['./home.component.css'] 11 | }) 12 | export class HomeComponent { 13 | logger = inject(LoggerService); 14 | auth = inject(AuthService); 15 | 16 | constructor() { 17 | this.logger.debug('home', 'debug'); 18 | this.logger.info('home', 'info'); 19 | this.logger.error('home', 'error'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | standalone: true, 5 | selector: 'app-navbar-cmp', 6 | templateUrl: 'navbar.component.html', 7 | }) 8 | export class NavbarComponent { 9 | sidebarVisible = false; 10 | 11 | sidebarToggle() { 12 | const body = document.getElementsByTagName('body')[0]; 13 | 14 | if (this.sidebarVisible == false) { 15 | body.classList.add('nav-open'); 16 | this.sidebarVisible = true; 17 | } else { 18 | this.sidebarVisible = false; 19 | body.classList.remove('nav-open'); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/not-found/not-found.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/app/shell/not-found/not-found.component.css -------------------------------------------------------------------------------- /apps/flights/src/app/shell/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 |

not-found works!

2 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'app-not-found', 6 | imports: [CommonModule], 7 | templateUrl: './not-found.component.html', 8 | styleUrls: ['./not-found.component.css'] 9 | }) 10 | export class NotFoundComponent {} 11 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/sidebar/sidebar.component.html: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /apps/flights/src/app/shell/sidebar/sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterLink } from '@angular/router'; 3 | 4 | @Component({ 5 | imports: [RouterLink], 6 | selector: 'app-sidebar-cmp', 7 | templateUrl: 'sidebar.component.html' 8 | }) 9 | export class SidebarComponent {} 10 | -------------------------------------------------------------------------------- /apps/flights/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/flights/src/assets/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "https://demo.angulararchitects.io/api" 3 | } 4 | -------------------------------------------------------------------------------- /apps/flights/src/assets/paper-design/angular2-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/assets/paper-design/angular2-logo.png -------------------------------------------------------------------------------- /apps/flights/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/flights/src/favicon.ico -------------------------------------------------------------------------------- /apps/flights/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flights 6 | 7 | 8 | 9 | 10 | 16 | 22 | 23 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apps/flights/src/main.ts: -------------------------------------------------------------------------------- 1 | import { provideHttpClient } from '@angular/common/http'; 2 | import { importProvidersFrom } from '@angular/core'; 3 | import { MatDialogModule } from '@angular/material/dialog'; 4 | import { bootstrapApplication } from '@angular/platform-browser'; 5 | import { provideRouter } from '@angular/router'; 6 | import { AppComponent } from './app/app.component'; 7 | import { APP_ROUTES } from './app/app.routes'; 8 | import { NextFlightsModule } from '@demo/ticketing/feature-next-flights'; 9 | import { LogLevel, provideLogger, withColor } from '@demo/shared/util-logger'; 10 | 11 | bootstrapApplication(AppComponent, { 12 | providers: [ 13 | provideHttpClient(), 14 | provideRouter(APP_ROUTES), 15 | importProvidersFrom(NextFlightsModule), 16 | importProvidersFrom(MatDialogModule), 17 | 18 | provideLogger( 19 | { 20 | level: LogLevel.DEBUG, 21 | }, 22 | withColor() 23 | ), 24 | ], 25 | }); 26 | -------------------------------------------------------------------------------- /apps/flights/src/styles.css: -------------------------------------------------------------------------------- 1 | .table-striped { 2 | width: 100%; 3 | border-collapse: collapse; 4 | margin: 20px 0; 5 | font-size: 16px; 6 | font-family: Arial, sans-serif; 7 | text-align: left; 8 | border-bottom: 1px solid #dee2e6; 9 | } 10 | 11 | .table-striped th, 12 | .table-striped td { 13 | padding: 12px 15px; 14 | border-top: 1px solid #dee2e6; 15 | } 16 | 17 | .table-striped th { 18 | background-color: #f8f9fa; 19 | color: #343a40; 20 | font-weight: bold; 21 | } 22 | 23 | .table-striped td { 24 | vertical-align: middle; 25 | } 26 | 27 | .title { 28 | font-size: 1.5rem; 29 | font-weight: bold; 30 | margin: 0; 31 | } 32 | 33 | h2.title { 34 | margin-bottom: 2rem; 35 | } 36 | 37 | .row { 38 | display: flex; 39 | flex-wrap: wrap; 40 | margin: -15px; 41 | } 42 | 43 | .col-xs-12, 44 | .col-sm-6, 45 | .col-md-4, 46 | .col-lg-3 { 47 | padding: 15px; 48 | box-sizing: border-box; 49 | } 50 | 51 | .col-xs-12 { 52 | flex: 0 0 100%; 53 | max-width: 100%; 54 | } 55 | 56 | @media (min-width: 576px) { 57 | .col-sm-6 { 58 | flex: 0 0 50%; 59 | max-width: 50%; 60 | } 61 | } 62 | 63 | @media (min-width: 768px) { 64 | .col-md-4 { 65 | flex: 0 0 33.333%; 66 | max-width: 33.333%; 67 | } 68 | } 69 | 70 | @media (min-width: 992px) { 71 | .col-lg-3 { 72 | flex: 0 0 25%; 73 | max-width: 25%; 74 | } 75 | } 76 | 77 | @media (max-width: 768px) { 78 | .sidebar { 79 | transform: translateX(-100%); 80 | } 81 | 82 | .sidebar.show { 83 | transform: translateX(0); 84 | } 85 | } 86 | 87 | .nav-open .sidebar { 88 | transform: translateX(0); 89 | } 90 | 91 | .nav-open { 92 | overflow: hidden; 93 | } 94 | 95 | .nav-open .overlay { 96 | display: block; 97 | position: fixed; 98 | top: 0; 99 | left: 0; 100 | width: 100%; 101 | height: 100%; 102 | background-color: rgba(0, 0, 0, 0.5); 103 | z-index: 9; 104 | transition: opacity 0.3s ease; 105 | } 106 | 107 | .hamburger-menu { 108 | display: none; 109 | flex-direction: column; 110 | justify-content: space-between; 111 | width: 30px; 112 | height: 25px; 113 | background: none; 114 | border: none; 115 | cursor: pointer; 116 | padding: 0; 117 | } 118 | 119 | .hamburger-menu span { 120 | display: block; 121 | width: 100%; 122 | height: 4px; 123 | background-color: white; 124 | border-radius: 2px; 125 | margin-bottom: 6px; 126 | transition: transform 0.3s ease, opacity 0.3s ease; 127 | } 128 | 129 | @media (max-width: 768px) { 130 | .hamburger-menu { 131 | display: inline; 132 | float: right; 133 | } 134 | } 135 | 136 | .card { 137 | background-color: white; 138 | border-radius: 4px; 139 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 140 | overflow: hidden; 141 | margin-bottom: 0rem; 142 | } 143 | 144 | .card-header { 145 | padding: 1rem; 146 | padding-bottom: 0; 147 | font-size: 1.25rem; 148 | font-weight: bold; 149 | } 150 | 151 | .card-body { 152 | padding: 1rem; 153 | padding-bottom: 0; 154 | } 155 | 156 | .cdk-dialog-container .card-body { 157 | margin-bottom: 2rem; 158 | } 159 | 160 | form { 161 | margin-bottom: 40px; 162 | } 163 | 164 | .form-group { 165 | margin-bottom: 1rem; 166 | } 167 | 168 | .form-control { 169 | width: 100%; 170 | padding: 0.5rem; 171 | font-size: 1rem; 172 | border: 1px solid #ccc; 173 | border-radius: 4px; 174 | } 175 | 176 | label { 177 | font-weight: bold; 178 | display: block; 179 | margin-bottom: 0.5rem; 180 | } 181 | 182 | .btn { 183 | display: inline-block; 184 | padding: 0.5rem 1rem; 185 | font-size: 1rem; 186 | font-weight: bold; 187 | color: #fff; 188 | background-color: #007bff; 189 | border: 1px solid transparent; 190 | border-radius: 4px; 191 | text-align: center; 192 | cursor: pointer; 193 | margin-right: 5px; 194 | } 195 | 196 | .btn-default { 197 | background-color: #6c757d; 198 | } 199 | 200 | .btn-danger { 201 | background-color: crimson; 202 | } 203 | 204 | .btn:hover { 205 | background-color: #0056b3; 206 | } 207 | 208 | .btn-danger:hover { 209 | background-color: #b01030; 210 | } 211 | 212 | .wrapper { 213 | display: flex; 214 | flex-direction: column; 215 | height: 100vh; 216 | overflow: hidden; 217 | background-color: #f4f5f7; 218 | } 219 | 220 | .navbar { 221 | display: flex; 222 | align-items: center; 223 | justify-content: space-between; 224 | padding: 0 1.5rem; 225 | height: 60px; 226 | color: white; 227 | background-color: #3f51b5; 228 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 229 | z-index: 10; 230 | position: fixed; 231 | top: 0; 232 | left: 0; 233 | right: 0; 234 | } 235 | 236 | .navbar .navbar-brand { 237 | font-size: 1.25rem; 238 | font-weight: bold; 239 | } 240 | 241 | .navbar .navbar-nav { 242 | display: flex; 243 | list-style: none; 244 | padding: 0; 245 | margin: 0; 246 | } 247 | 248 | .navbar .nav-item { 249 | margin-left: 1rem; 250 | } 251 | 252 | .navbar .nav-link { 253 | text-decoration: none; 254 | color: white; 255 | font-size: 1rem; 256 | transition: color 0.3s; 257 | } 258 | 259 | .navbar .nav-link:hover { 260 | color: #ffca28; 261 | } 262 | 263 | .sidebar { 264 | width: 250px; 265 | background-color: #ffffff; 266 | box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); 267 | display: flex; 268 | flex-direction: column; 269 | padding: 1rem 0; 270 | overflow-y: auto; 271 | position: fixed; 272 | top: 60px; 273 | bottom: 0; 274 | left: 0; 275 | transition: transform 0.3s ease; 276 | } 277 | 278 | .sidebar .logo { 279 | text-align: center; 280 | font-size: 1.25rem; 281 | font-weight: bold; 282 | margin-bottom: 1rem; 283 | color: #3f51b5; 284 | } 285 | 286 | .sidebar .nav { 287 | list-style: none; 288 | padding: 0; 289 | margin: 0; 290 | } 291 | 292 | .sidebar .nav li { 293 | margin-bottom: 0.5rem; 294 | } 295 | 296 | .sidebar .nav a { 297 | text-decoration: none; 298 | display: block; 299 | padding: 0.75rem 1.5rem; 300 | font-size: 1rem; 301 | color: #333333; 302 | border-radius: 4px; 303 | transition: background-color 0.3s, color 0.3s; 304 | } 305 | 306 | .sidebar .nav a:hover { 307 | background-color: #e8eaf6; 308 | color: #3f51b5; 309 | } 310 | 311 | .sidebar .nav a.active { 312 | background-color: #3f51b5; 313 | color: #ffffff; 314 | } 315 | 316 | .main-panel { 317 | margin-left: 250px; 318 | margin-top: 60px; 319 | padding: 2rem; 320 | flex: 1; 321 | overflow-y: auto; 322 | transition: margin-left 0.3s ease; 323 | } 324 | 325 | @media (max-width: 768px) { 326 | .main-panel { 327 | margin-left: 0; 328 | } 329 | } 330 | 331 | .app-title { 332 | display: flex; 333 | align-items: center; 334 | gap: 10px; 335 | font-weight: bold; 336 | font-size: 1.2em; 337 | } 338 | 339 | .card:has(.nav) { 340 | margin-bottom: 20px; 341 | } 342 | 343 | .nav-secondary { 344 | display: flex; 345 | list-style: none; 346 | padding: 0; 347 | margin: 0; 348 | font-size: 1rem; 349 | margin-bottom: 10px; 350 | } 351 | 352 | .nav-secondary li { 353 | display: inline-block; 354 | } 355 | 356 | .nav-secondary a { 357 | text-decoration: none; 358 | color: #3f51b5; 359 | } 360 | 361 | /* --- */ 362 | 363 | input.ng-dirty.ng-invalid { 364 | border-left: darkred 5px solid; 365 | } 366 | 367 | input.ng-dirty.ng-valid { 368 | border-left: darkgreen 5px solid; 369 | } 370 | 371 | .selected { 372 | background-color: moccasin; 373 | } 374 | 375 | a { 376 | cursor: pointer; 377 | } 378 | 379 | .status { 380 | text-decoration: underline; 381 | color: navy; 382 | } 383 | 384 | .status:hover { 385 | text-decoration: underline; 386 | color: darkgoldenrod; 387 | } 388 | 389 | .p40 { 390 | padding: 20px; 391 | } 392 | 393 | html, 394 | body { 395 | height: 100%; 396 | } 397 | body { 398 | margin: 0; 399 | font-family: Roboto, 'Helvetica Neue', sans-serif; 400 | } 401 | -------------------------------------------------------------------------------- /apps/flights/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/flights/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/flights/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "useDefineForClassFields": false, 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.app.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | }, 21 | { 22 | "path": "./tsconfig.editor.json" 23 | } 24 | ], 25 | "extends": "../../tsconfig.base.json", 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/flights/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["jasmine"] 6 | }, 7 | "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/miles/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "flightDemo", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "flight-demo", 25 | "style": "kebab-case" 26 | } 27 | ], 28 | "@angular-eslint/prefer-standalone": "off" 29 | } 30 | }, 31 | { 32 | "files": ["*.html"], 33 | "extends": ["plugin:@nx/angular-template"], 34 | "rules": {} 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /apps/miles/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'miles', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | coverageDirectory: '../../coverage/apps/miles', 7 | transform: { 8 | '^.+\\.(ts|mjs|js|html)$': [ 9 | 'jest-preset-angular', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | stringifyContentPathRegex: '\\.(html|svg)$', 13 | }, 14 | ], 15 | }, 16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 17 | snapshotSerializers: [ 18 | 'jest-preset-angular/build/serializers/no-ng-attributes', 19 | 'jest-preset-angular/build/serializers/ng-snapshot', 20 | 'jest-preset-angular/build/serializers/html-comment', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /apps/miles/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "miles", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "prefix": "flight-demo", 6 | "sourceRoot": "apps/miles/src", 7 | "tags": [], 8 | "targets": { 9 | "build": { 10 | "executor": "@angular-devkit/build-angular:application", 11 | "outputs": ["{options.outputPath}"], 12 | "options": { 13 | "outputPath": "dist/apps/miles", 14 | "index": "apps/miles/src/index.html", 15 | "browser": "apps/miles/src/main.ts", 16 | "polyfills": ["zone.js"], 17 | "tsConfig": "apps/miles/tsconfig.app.json", 18 | "assets": ["apps/miles/src/favicon.ico", "apps/miles/src/assets"], 19 | "styles": ["apps/miles/src/styles.css"], 20 | "scripts": [] 21 | }, 22 | "configurations": { 23 | "production": { 24 | "budgets": [ 25 | { 26 | "type": "initial", 27 | "maximumWarning": "500kb", 28 | "maximumError": "1mb" 29 | }, 30 | { 31 | "type": "anyComponentStyle", 32 | "maximumWarning": "2kb", 33 | "maximumError": "4kb" 34 | } 35 | ], 36 | "outputHashing": "all" 37 | }, 38 | "development": { 39 | "optimization": false, 40 | "extractLicenses": false, 41 | "sourceMap": true 42 | } 43 | }, 44 | "defaultConfiguration": "production" 45 | }, 46 | "serve": { 47 | "executor": "@angular-devkit/build-angular:dev-server", 48 | "configurations": { 49 | "production": { 50 | "buildTarget": "miles:build:production" 51 | }, 52 | "development": { 53 | "buildTarget": "miles:build:development" 54 | } 55 | }, 56 | "defaultConfiguration": "development" 57 | }, 58 | "extract-i18n": { 59 | "executor": "@angular-devkit/build-angular:extract-i18n", 60 | "options": { 61 | "buildTarget": "miles:build" 62 | } 63 | }, 64 | "lint": { 65 | "executor": "@nx/eslint:lint" 66 | }, 67 | "test": { 68 | "executor": "@nx/jest:jest", 69 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 70 | "options": { 71 | "jestConfig": "apps/miles/jest.config.ts" 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /apps/miles/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/miles/src/app/app.component.css -------------------------------------------------------------------------------- /apps/miles/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /apps/miles/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { NxWelcomeComponent } from './nx-welcome.component'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async () => { 8 | await TestBed.configureTestingModule({ 9 | imports: [AppComponent, NxWelcomeComponent, RouterTestingModule], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should render title', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | fixture.detectChanges(); 16 | const compiled = fixture.nativeElement as HTMLElement; 17 | expect(compiled.querySelector('h1')?.textContent).toContain( 18 | 'Welcome miles' 19 | ); 20 | }); 21 | 22 | it(`should have as title 'miles'`, () => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.componentInstance; 25 | expect(app.title).toEqual('miles'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /apps/miles/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { NxWelcomeComponent } from './nx-welcome.component'; 4 | 5 | @Component({ 6 | imports: [NxWelcomeComponent, RouterModule], 7 | selector: 'flight-demo-root', 8 | templateUrl: './app.component.html', 9 | styleUrls: ['./app.component.css'] 10 | }) 11 | export class AppComponent { 12 | title = 'miles'; 13 | } 14 | -------------------------------------------------------------------------------- /apps/miles/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | import { appRoutes } from './app.routes'; 4 | 5 | export const appConfig: ApplicationConfig = { 6 | providers: [provideRouter(appRoutes)], 7 | }; 8 | -------------------------------------------------------------------------------- /apps/miles/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Route } from '@angular/router'; 2 | 3 | export const appRoutes: Route[] = []; 4 | -------------------------------------------------------------------------------- /apps/miles/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/miles/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/miles/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/apps/miles/src/favicon.ico -------------------------------------------------------------------------------- /apps/miles/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | miles 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/miles/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig).catch((err) => 6 | console.error(err) 7 | ); 8 | -------------------------------------------------------------------------------- /apps/miles/src/styles.css: -------------------------------------------------------------------------------- 1 | .table-striped { 2 | width: 100%; 3 | border-collapse: collapse; 4 | margin: 20px 0; 5 | font-size: 16px; 6 | font-family: Arial, sans-serif; 7 | text-align: left; 8 | border-bottom: 1px solid #dee2e6; 9 | } 10 | 11 | .table-striped th, 12 | .table-striped td { 13 | padding: 12px 15px; 14 | border-top: 1px solid #dee2e6; 15 | } 16 | 17 | .table-striped th { 18 | background-color: #f8f9fa; 19 | color: #343a40; 20 | font-weight: bold; 21 | } 22 | 23 | .table-striped td { 24 | vertical-align: middle; 25 | } 26 | 27 | .title { 28 | font-size: 1.5rem; 29 | font-weight: bold; 30 | margin: 0; 31 | } 32 | 33 | h2.title { 34 | margin-bottom: 2rem; 35 | } 36 | 37 | .row { 38 | display: flex; 39 | flex-wrap: wrap; 40 | margin: -15px; 41 | } 42 | 43 | .col-xs-12, 44 | .col-sm-6, 45 | .col-md-4, 46 | .col-lg-3 { 47 | padding: 15px; 48 | box-sizing: border-box; 49 | } 50 | 51 | .col-xs-12 { 52 | flex: 0 0 100%; 53 | max-width: 100%; 54 | } 55 | 56 | @media (min-width: 576px) { 57 | .col-sm-6 { 58 | flex: 0 0 50%; 59 | max-width: 50%; 60 | } 61 | } 62 | 63 | @media (min-width: 768px) { 64 | .col-md-4 { 65 | flex: 0 0 33.333%; 66 | max-width: 33.333%; 67 | } 68 | } 69 | 70 | @media (min-width: 992px) { 71 | .col-lg-3 { 72 | flex: 0 0 25%; 73 | max-width: 25%; 74 | } 75 | } 76 | 77 | @media (max-width: 768px) { 78 | .sidebar { 79 | transform: translateX(-100%); 80 | } 81 | 82 | .sidebar.show { 83 | transform: translateX(0); 84 | } 85 | } 86 | 87 | .nav-open .sidebar { 88 | transform: translateX(0); 89 | } 90 | 91 | .nav-open { 92 | overflow: hidden; 93 | } 94 | 95 | .nav-open .overlay { 96 | display: block; 97 | position: fixed; 98 | top: 0; 99 | left: 0; 100 | width: 100%; 101 | height: 100%; 102 | background-color: rgba(0, 0, 0, 0.5); 103 | z-index: 9; 104 | transition: opacity 0.3s ease; 105 | } 106 | 107 | .hamburger-menu { 108 | display: none; 109 | flex-direction: column; 110 | justify-content: space-between; 111 | width: 30px; 112 | height: 25px; 113 | background: none; 114 | border: none; 115 | cursor: pointer; 116 | padding: 0; 117 | } 118 | 119 | .hamburger-menu span { 120 | display: block; 121 | width: 100%; 122 | height: 4px; 123 | background-color: white; 124 | border-radius: 2px; 125 | margin-bottom: 6px; 126 | transition: transform 0.3s ease, opacity 0.3s ease; 127 | } 128 | 129 | @media (max-width: 768px) { 130 | .hamburger-menu { 131 | display: inline; 132 | float: right; 133 | } 134 | } 135 | 136 | .card { 137 | background-color: white; 138 | border-radius: 4px; 139 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 140 | overflow: hidden; 141 | margin-bottom: 0rem; 142 | } 143 | 144 | .card-header { 145 | padding: 1rem; 146 | padding-bottom: 0; 147 | font-size: 1.25rem; 148 | font-weight: bold; 149 | } 150 | 151 | .card-body { 152 | padding: 1rem; 153 | padding-bottom: 0; 154 | } 155 | 156 | .cdk-dialog-container .card-body { 157 | margin-bottom: 2rem; 158 | } 159 | 160 | form { 161 | margin-bottom: 40px; 162 | } 163 | 164 | .form-group { 165 | margin-bottom: 1rem; 166 | } 167 | 168 | .form-control { 169 | width: 100%; 170 | padding: 0.5rem; 171 | font-size: 1rem; 172 | border: 1px solid #ccc; 173 | border-radius: 4px; 174 | } 175 | 176 | label { 177 | font-weight: bold; 178 | display: block; 179 | margin-bottom: 0.5rem; 180 | } 181 | 182 | .btn { 183 | display: inline-block; 184 | padding: 0.5rem 1rem; 185 | font-size: 1rem; 186 | font-weight: bold; 187 | color: #fff; 188 | background-color: #007bff; 189 | border: 1px solid transparent; 190 | border-radius: 4px; 191 | text-align: center; 192 | cursor: pointer; 193 | margin-right: 5px; 194 | } 195 | 196 | .btn-default { 197 | background-color: #6c757d; 198 | } 199 | 200 | .btn-danger { 201 | background-color: crimson; 202 | } 203 | 204 | .btn:hover { 205 | background-color: #0056b3; 206 | } 207 | 208 | .btn-danger:hover { 209 | background-color: #b01030; 210 | } 211 | 212 | .wrapper { 213 | display: flex; 214 | flex-direction: column; 215 | height: 100vh; 216 | overflow: hidden; 217 | background-color: #f4f5f7; 218 | } 219 | 220 | .navbar { 221 | display: flex; 222 | align-items: center; 223 | justify-content: space-between; 224 | padding: 0 1.5rem; 225 | height: 60px; 226 | color: white; 227 | background-color: #3f51b5; 228 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 229 | z-index: 10; 230 | position: fixed; 231 | top: 0; 232 | left: 0; 233 | right: 0; 234 | } 235 | 236 | .navbar .navbar-brand { 237 | font-size: 1.25rem; 238 | font-weight: bold; 239 | } 240 | 241 | .navbar .navbar-nav { 242 | display: flex; 243 | list-style: none; 244 | padding: 0; 245 | margin: 0; 246 | } 247 | 248 | .navbar .nav-item { 249 | margin-left: 1rem; 250 | } 251 | 252 | .navbar .nav-link { 253 | text-decoration: none; 254 | color: white; 255 | font-size: 1rem; 256 | transition: color 0.3s; 257 | } 258 | 259 | .navbar .nav-link:hover { 260 | color: #ffca28; 261 | } 262 | 263 | .sidebar { 264 | width: 250px; 265 | background-color: #ffffff; 266 | box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1); 267 | display: flex; 268 | flex-direction: column; 269 | padding: 1rem 0; 270 | overflow-y: auto; 271 | position: fixed; 272 | top: 60px; 273 | bottom: 0; 274 | left: 0; 275 | transition: transform 0.3s ease; 276 | } 277 | 278 | .sidebar .logo { 279 | text-align: center; 280 | font-size: 1.25rem; 281 | font-weight: bold; 282 | margin-bottom: 1rem; 283 | color: #3f51b5; 284 | } 285 | 286 | .sidebar .nav { 287 | list-style: none; 288 | padding: 0; 289 | margin: 0; 290 | } 291 | 292 | .sidebar .nav li { 293 | margin-bottom: 0.5rem; 294 | } 295 | 296 | .sidebar .nav a { 297 | text-decoration: none; 298 | display: block; 299 | padding: 0.75rem 1.5rem; 300 | font-size: 1rem; 301 | color: #333333; 302 | border-radius: 4px; 303 | transition: background-color 0.3s, color 0.3s; 304 | } 305 | 306 | .sidebar .nav a:hover { 307 | background-color: #e8eaf6; 308 | color: #3f51b5; 309 | } 310 | 311 | .sidebar .nav a.active { 312 | background-color: #3f51b5; 313 | color: #ffffff; 314 | } 315 | 316 | .main-panel { 317 | margin-left: 250px; 318 | margin-top: 60px; 319 | padding: 2rem; 320 | flex: 1; 321 | overflow-y: auto; 322 | transition: margin-left 0.3s ease; 323 | } 324 | 325 | @media (max-width: 768px) { 326 | .main-panel { 327 | margin-left: 0; 328 | } 329 | } 330 | 331 | .app-title { 332 | display: flex; 333 | align-items: center; 334 | gap: 10px; 335 | font-weight: bold; 336 | font-size: 1.2em; 337 | } 338 | 339 | .card:has(.nav) { 340 | margin-bottom: 20px; 341 | } 342 | 343 | .nav-secondary { 344 | display: flex; 345 | list-style: none; 346 | padding: 0; 347 | margin: 0; 348 | font-size: 1rem; 349 | margin-bottom: 10px; 350 | } 351 | 352 | .nav-secondary li { 353 | display: inline-block; 354 | } 355 | 356 | .nav-secondary a { 357 | text-decoration: none; 358 | color: #3f51b5; 359 | } 360 | 361 | /* --- */ 362 | 363 | input.ng-dirty.ng-invalid { 364 | border-left: darkred 5px solid; 365 | } 366 | 367 | input.ng-dirty.ng-valid { 368 | border-left: darkgreen 5px solid; 369 | } 370 | 371 | .selected { 372 | background-color: moccasin; 373 | } 374 | 375 | a { 376 | cursor: pointer; 377 | } 378 | 379 | .status { 380 | text-decoration: underline; 381 | color: navy; 382 | } 383 | 384 | .status:hover { 385 | text-decoration: underline; 386 | color: darkgoldenrod; 387 | } 388 | 389 | .p40 { 390 | padding: 20px; 391 | } 392 | 393 | html, 394 | body { 395 | height: 100%; 396 | } 397 | body { 398 | margin: 0; 399 | font-family: Roboto, 'Helvetica Neue', sans-serif; 400 | } 401 | -------------------------------------------------------------------------------- /apps/miles/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment 2 | globalThis.ngJest = { 3 | testEnvironmentOptions: { 4 | errorOnUnknownElements: true, 5 | errorOnUnknownProperties: true, 6 | }, 7 | }; 8 | import 'jest-preset-angular/setup-jest'; 9 | -------------------------------------------------------------------------------- /apps/miles/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/miles/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/miles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "useDefineForClassFields": false, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true 12 | }, 13 | "files": [], 14 | "include": [], 15 | "references": [ 16 | { 17 | "path": "./tsconfig.app.json" 18 | }, 19 | { 20 | "path": "./tsconfig.spec.json" 21 | }, 22 | { 23 | "path": "./tsconfig.editor.json" 24 | } 25 | ], 26 | "extends": "../../tsconfig.base.json", 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/miles/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "target": "es2016", 7 | "types": ["jest", "node"] 8 | }, 9 | "files": ["src/test-setup.ts"], 10 | "include": [ 11 | "jest.config.ts", 12 | "src/**/*.test.ts", 13 | "src/**/*.spec.ts", 14 | "src/**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjectsAsync } from '@nx/jest'; 2 | 3 | export default async () => ({ 4 | projects: await getJestProjectsAsync(), 5 | }); 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | const { join } = require('path'); 5 | const { constants } = require('karma'); 6 | 7 | module.exports = () => { 8 | return { 9 | basePath: '', 10 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 11 | plugins: [ 12 | require('karma-jasmine'), 13 | require('karma-chrome-launcher'), 14 | require('karma-jasmine-html-reporter'), 15 | require('karma-coverage'), 16 | require('@angular-devkit/build-angular/plugins/karma'), 17 | ], 18 | client: { 19 | jasmine: { 20 | // you can add configuration options for Jasmine here 21 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 22 | // for example, you can disable the random execution with `random: false` 23 | // or set a specific seed with `seed: 4321` 24 | }, 25 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 26 | }, 27 | jasmineHtmlReporter: { 28 | suppressAll: true, // removes the duplicated traces 29 | }, 30 | coverageReporter: { 31 | dir: join(__dirname, './coverage'), 32 | subdir: '.', 33 | reporters: [{ type: 'html' }, { type: 'text-summary' }], 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: constants.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: true, 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/libs/.gitkeep -------------------------------------------------------------------------------- /libs/ui-common/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts"], 7 | "extends": [ 8 | "plugin:@nx/angular", 9 | "plugin:@angular-eslint/template/process-inline-templates" 10 | ], 11 | "rules": { 12 | "@angular-eslint/directive-selector": [ 13 | "error", 14 | { 15 | "type": "attribute", 16 | "prefix": "flightDemo", 17 | "style": "camelCase" 18 | } 19 | ], 20 | "@angular-eslint/component-selector": [ 21 | "error", 22 | { 23 | "type": "element", 24 | "prefix": "flight-demo", 25 | "style": "kebab-case" 26 | } 27 | ], 28 | "@angular-eslint/prefer-standalone": "off" 29 | } 30 | }, 31 | { 32 | "files": ["*.html"], 33 | "extends": ["plugin:@nx/angular-template"], 34 | "rules": {} 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /libs/ui-common/README.md: -------------------------------------------------------------------------------- 1 | # ui-common 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test ui-common` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/ui-common/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'ui-common', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | coverageDirectory: '../../coverage/libs/ui-common', 7 | transform: { 8 | '^.+\\.(ts|mjs|js|html)$': [ 9 | 'jest-preset-angular', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | stringifyContentPathRegex: '\\.(html|svg)$', 13 | }, 14 | ], 15 | }, 16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 17 | snapshotSerializers: [ 18 | 'jest-preset-angular/build/serializers/no-ng-attributes', 19 | 'jest-preset-angular/build/serializers/ng-snapshot', 20 | 'jest-preset-angular/build/serializers/html-comment', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /libs/ui-common/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-common", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libs/ui-common/src", 5 | "prefix": "flight-demo", 6 | "projectType": "library", 7 | "tags": [], 8 | "targets": { 9 | "test": { 10 | "executor": "@nx/jest:jest", 11 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 12 | "options": { 13 | "jestConfig": "libs/ui-common/jest.config.ts" 14 | } 15 | }, 16 | "lint": { 17 | "executor": "@nx/eslint:lint" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libs/ui-common/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/ui-common/ui-common.component'; 2 | -------------------------------------------------------------------------------- /libs/ui-common/src/lib/ui-common/ui-common.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/libs/ui-common/src/lib/ui-common/ui-common.component.css -------------------------------------------------------------------------------- /libs/ui-common/src/lib/ui-common/ui-common.component.html: -------------------------------------------------------------------------------- 1 |

ui-common works!

2 | -------------------------------------------------------------------------------- /libs/ui-common/src/lib/ui-common/ui-common.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { UiCommonComponent } from './ui-common.component'; 3 | 4 | describe('UiCommonComponent', () => { 5 | let component: UiCommonComponent; 6 | let fixture: ComponentFixture; 7 | 8 | beforeEach(async () => { 9 | await TestBed.configureTestingModule({ 10 | imports: [UiCommonComponent], 11 | }).compileComponents(); 12 | 13 | fixture = TestBed.createComponent(UiCommonComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /libs/ui-common/src/lib/ui-common/ui-common.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @Component({ 5 | selector: 'flight-demo-ui-common', 6 | imports: [CommonModule], 7 | templateUrl: './ui-common.component.html', 8 | styleUrls: ['./ui-common.component.css'] 9 | }) 10 | export class UiCommonComponent {} 11 | -------------------------------------------------------------------------------- /libs/ui-common/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment 2 | globalThis.ngJest = { 3 | testEnvironmentOptions: { 4 | errorOnUnknownElements: true, 5 | errorOnUnknownProperties: true, 6 | }, 7 | }; 8 | import 'jest-preset-angular/setup-jest'; 9 | -------------------------------------------------------------------------------- /libs/ui-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "useDefineForClassFields": false, 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ], 22 | "extends": "../../tsconfig.base.json", 23 | "angularCompilerOptions": { 24 | "enableI18nLegacyMessageIdFormat": false, 25 | "strictInjectionParameters": true, 26 | "strictInputAccessModifiers": true, 27 | "strictTemplates": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libs/ui-common/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true, 8 | "types": [] 9 | }, 10 | "exclude": [ 11 | "src/**/*.spec.ts", 12 | "src/test-setup.ts", 13 | "jest.config.ts", 14 | "src/**/*.test.ts" 15 | ], 16 | "include": ["src/**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /libs/ui-common/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "target": "es2016", 7 | "types": ["jest", "node"] 8 | }, 9 | "files": ["src/test-setup.ts"], 10 | "include": [ 11 | "jest.config.ts", 12 | "src/**/*.test.ts", 13 | "src/**/*.spec.ts", 14 | "src/**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "cli": "nx", 5 | "version": "17.3.0-beta.6", 6 | "description": "Updates the nx wrapper.", 7 | "implementation": "./src/migrations/update-17-3-0/update-nxw", 8 | "package": "nx", 9 | "name": "17.3.0-update-nx-wrapper" 10 | }, 11 | { 12 | "cli": "nx", 13 | "version": "18.0.0-beta.2", 14 | "description": "Updates nx.json to disabled adding plugins when generating projects in an existing Nx workspace", 15 | "implementation": "./src/migrations/update-18-0-0/disable-crystal-for-existing-workspaces", 16 | "x-repair-skip": true, 17 | "package": "nx", 18 | "name": "18.0.0-disable-adding-plugins-for-existing-workspaces" 19 | }, 20 | { 21 | "version": "18.1.0-beta.3", 22 | "description": "Moves affected.defaultBase to defaultBase in `nx.json`", 23 | "implementation": "./src/migrations/update-17-2-0/move-default-base", 24 | "package": "nx", 25 | "name": "move-default-base-to-nx-json-root" 26 | }, 27 | { 28 | "cli": "nx", 29 | "version": "19.2.0-beta.2", 30 | "description": "Updates the default workspace data directory to .nx/workspace-data", 31 | "implementation": "./src/migrations/update-19-2-0/move-workspace-data-directory", 32 | "package": "nx", 33 | "name": "19-2-0-move-graph-cache-directory" 34 | }, 35 | { 36 | "cli": "nx", 37 | "version": "19.2.2-beta.0", 38 | "description": "Updates the nx wrapper.", 39 | "implementation": "./src/migrations/update-17-3-0/update-nxw", 40 | "package": "nx", 41 | "name": "19-2-2-update-nx-wrapper" 42 | }, 43 | { 44 | "version": "19.2.4-beta.0", 45 | "description": "Set project name in nx.json explicitly", 46 | "implementation": "./src/migrations/update-19-2-4/set-project-name", 47 | "x-repair-skip": true, 48 | "package": "nx", 49 | "name": "19-2-4-set-project-name" 50 | }, 51 | { 52 | "version": "20.0.0-beta.7", 53 | "description": "Migration for v20.0.0-beta.7", 54 | "implementation": "./src/migrations/update-20-0-0/move-use-daemon-process", 55 | "package": "nx", 56 | "name": "move-use-daemon-process" 57 | }, 58 | { 59 | "version": "20.0.1", 60 | "description": "Set `useLegacyCache` to true for migrating workspaces", 61 | "implementation": "./src/migrations/update-20-0-1/use-legacy-cache", 62 | "x-repair-skip": true, 63 | "package": "nx", 64 | "name": "use-legacy-cache" 65 | }, 66 | { 67 | "cli": "nx", 68 | "version": "20.0.0-beta.5", 69 | "description": "replace getJestProjects with getJestProjectsAsync", 70 | "implementation": "./src/migrations/update-20-0-0/replace-getJestProjects-with-getJestProjectsAsync", 71 | "package": "@nx/jest", 72 | "name": "replace-getJestProjects-with-getJestProjectsAsync" 73 | }, 74 | { 75 | "cli": "nx", 76 | "version": "17.2.6-beta.1", 77 | "description": "Rename workspace rules from @nx/workspace/name to @nx/workspace-name", 78 | "implementation": "./src/migrations/update-17-2-6-rename-workspace-rules/rename-workspace-rules", 79 | "package": "@nx/eslint-plugin", 80 | "name": "update-17-2-6-rename-workspace-rules" 81 | }, 82 | { 83 | "cli": "nx", 84 | "version": "19.1.0-beta.6", 85 | "description": "Migrate no-extra-semi rules into user config, out of nx extendable configs", 86 | "implementation": "./src/migrations/update-19-1-0-migrate-no-extra-semi/migrate-no-extra-semi", 87 | "package": "@nx/eslint-plugin", 88 | "name": "update-19-1-0-rename-no-extra-semi" 89 | }, 90 | { 91 | "version": "17.2.0-beta.0", 92 | "description": "Simplify eslintFilePatterns", 93 | "implementation": "./src/migrations/update-17-2-0/simplify-eslint-patterns", 94 | "package": "@nx/eslint", 95 | "name": "simplify-eslint-patterns" 96 | }, 97 | { 98 | "version": "17.2.9", 99 | "description": "Move executor options to target defaults", 100 | "implementation": "./src/migrations/update-17-2-9/move-options-to-target-defaults", 101 | "package": "@nx/eslint", 102 | "name": "move-options-to-target-defaults" 103 | }, 104 | { 105 | "version": "20.2.0-beta.5", 106 | "description": "Update TypeScript ESLint packages to v8.13.0 if they are already on v8", 107 | "implementation": "./src/migrations/update-20-2-0/update-typescript-eslint-v8-13-0", 108 | "package": "@nx/eslint", 109 | "name": "update-typescript-eslint-v8.13.0" 110 | }, 111 | { 112 | "version": "20.3.0-beta.1", 113 | "description": "Update ESLint flat config to include .cjs, .mjs, .cts, and .mts files in overrides (if needed)", 114 | "implementation": "./src/migrations/update-20-3-0/add-file-extensions-to-overrides", 115 | "package": "@nx/eslint", 116 | "name": "add-file-extensions-to-overrides" 117 | }, 118 | { 119 | "cli": "nx", 120 | "version": "18.1.0-beta.3", 121 | "description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.", 122 | "implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6", 123 | "package": "@nx/cypress", 124 | "name": "update-cypress-version-13-6-6" 125 | }, 126 | { 127 | "cli": "nx", 128 | "version": "19.6.0-beta.4", 129 | "description": "Update ciWebServerCommand to use static serve for the application.", 130 | "implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-static-serve", 131 | "package": "@nx/cypress", 132 | "name": "update-19-6-0-update-ci-webserver-for-vite" 133 | }, 134 | { 135 | "cli": "nx", 136 | "version": "17.2.0-beta.2", 137 | "description": "Rename '@nx/angular:webpack-dev-server' executor to '@nx/angular:dev-server'", 138 | "factory": "./src/migrations/update-17-2-0/rename-webpack-dev-server", 139 | "package": "@nx/angular", 140 | "name": "rename-webpack-dev-server-executor" 141 | }, 142 | { 143 | "cli": "nx", 144 | "version": "17.3.0-beta.10", 145 | "requires": { "@angular/core": ">=17.1.0" }, 146 | "description": "Update the @angular/cli package version to ~17.1.0.", 147 | "factory": "./src/migrations/update-17-3-0/update-angular-cli", 148 | "package": "@nx/angular", 149 | "name": "update-angular-cli-version-17-1-0" 150 | }, 151 | { 152 | "cli": "nx", 153 | "version": "17.3.0-beta.10", 154 | "requires": { "@angular/core": ">=17.1.0" }, 155 | "description": "Add 'browser-sync' as dev dependency when '@angular-devkit/build-angular:ssr-dev-server' or '@nx/angular:module-federation-dev-ssr' is used.", 156 | "factory": "./src/migrations/update-17-3-0/add-browser-sync-dependency", 157 | "package": "@nx/angular", 158 | "name": "add-browser-sync-dependency" 159 | }, 160 | { 161 | "cli": "nx", 162 | "version": "17.3.0-beta.10", 163 | "requires": { "@angular/core": ">=17.1.0" }, 164 | "description": "Add 'autoprefixer' as dev dependency when '@nx/angular:ng-packagr-lite' or '@nx/angular:package` is used.", 165 | "factory": "./src/migrations/update-17-3-0/add-autoprefixer-dependency", 166 | "package": "@nx/angular", 167 | "name": "add-autoprefixer-dependency" 168 | }, 169 | { 170 | "cli": "nx", 171 | "version": "18.0.0-beta.0", 172 | "description": "Add NX_MF_DEV_SERVER_STATIC_REMOTES to inputs for task hashing when '@nx/angular:webpack-browser' is used for Module Federation.", 173 | "factory": "./src/migrations/update-18-0-0/add-mf-env-var-to-target-defaults", 174 | "package": "@nx/angular", 175 | "name": "add-module-federation-env-var-to-target-defaults" 176 | }, 177 | { 178 | "cli": "nx", 179 | "version": "18.1.0-beta.1", 180 | "requires": { "@angular/core": ">=17.2.0" }, 181 | "description": "Update the @angular/cli package version to ~17.2.0.", 182 | "factory": "./src/migrations/update-18-1-0/update-angular-cli", 183 | "package": "@nx/angular", 184 | "name": "update-angular-cli-version-17-2-0" 185 | }, 186 | { 187 | "cli": "nx", 188 | "version": "18.1.1-beta.0", 189 | "description": "Ensure targetDefaults inputs for task hashing when '@nx/angular:webpack-browser' is used are correct for Module Federation.", 190 | "factory": "./src/migrations/update-18-1-1/fix-target-defaults-inputs", 191 | "package": "@nx/angular", 192 | "name": "fix-target-defaults-for-webpack-browser" 193 | }, 194 | { 195 | "cli": "nx", 196 | "version": "18.2.0-beta.0", 197 | "requires": { "@angular/core": ">=17.3.0" }, 198 | "description": "Update the @angular/cli package version to ~17.3.0.", 199 | "factory": "./src/migrations/update-18-2-0/update-angular-cli", 200 | "package": "@nx/angular", 201 | "name": "update-angular-cli-version-17-3-0" 202 | }, 203 | { 204 | "cli": "nx", 205 | "version": "19.1.0-beta.2", 206 | "requires": { "@angular/core": ">=18.0.0" }, 207 | "description": "Update the @angular/cli package version to ~18.0.0.", 208 | "factory": "./src/migrations/update-19-1-0/update-angular-cli", 209 | "package": "@nx/angular", 210 | "name": "update-angular-cli-version-18-0-0" 211 | }, 212 | { 213 | "cli": "nx", 214 | "version": "19.2.1-beta.0", 215 | "requires": { "@angular-eslint/eslint-plugin": ">=18.0.0" }, 216 | "description": "Installs the '@typescript-eslint/utils' package when having installed '@angular-eslint/eslint-plugin' or '@angular-eslint/eslint-plugin-template' with version >=18.0.0.", 217 | "factory": "./src/migrations/update-19-2-1/add-typescript-eslint-utils", 218 | "package": "@nx/angular", 219 | "name": "add-typescript-eslint-utils" 220 | }, 221 | { 222 | "cli": "nx", 223 | "version": "19.5.0-beta.1", 224 | "requires": { "@angular/core": ">=18.1.0" }, 225 | "description": "Update the @angular/cli package version to ~18.1.0.", 226 | "factory": "./src/migrations/update-19-5-0/update-angular-cli", 227 | "package": "@nx/angular", 228 | "name": "update-angular-cli-version-18-1-0" 229 | }, 230 | { 231 | "cli": "nx", 232 | "version": "19.6.0-beta.4", 233 | "description": "Ensure Module Federation DTS is turned off by default.", 234 | "factory": "./src/migrations/update-19-6-0/turn-off-dts-by-default", 235 | "package": "@nx/angular", 236 | "name": "update-19-6-0" 237 | }, 238 | { 239 | "cli": "nx", 240 | "version": "19.6.0-beta.7", 241 | "requires": { "@angular/core": ">=18.2.0" }, 242 | "description": "Update the @angular/cli package version to ~18.2.0.", 243 | "factory": "./src/migrations/update-19-6-0/update-angular-cli", 244 | "package": "@nx/angular", 245 | "name": "update-angular-cli-version-18-2-0" 246 | }, 247 | { 248 | "cli": "nx", 249 | "version": "19.6.1-beta.0", 250 | "description": "Ensure Target Defaults are set correctly for Module Federation.", 251 | "factory": "./src/migrations/update-19-6-1/ensure-depends-on-for-mf", 252 | "package": "@nx/angular", 253 | "name": "update-19-6-1-ensure-module-federation-target-defaults" 254 | }, 255 | { 256 | "cli": "nx", 257 | "version": "20.2.0-beta.2", 258 | "description": "Update the ModuleFederationConfig import use @nx/module-federation.", 259 | "factory": "./src/migrations/update-20-2-0/migrate-mf-imports-to-new-package", 260 | "package": "@nx/angular", 261 | "name": "update-20-2-0-update-module-federation-config-import" 262 | }, 263 | { 264 | "cli": "nx", 265 | "version": "20.2.0-beta.2", 266 | "description": "Update the withModuleFederation import use @nx/module-federation/angular.", 267 | "factory": "./src/migrations/update-20-2-0/migrate-with-mf-import-to-new-package", 268 | "package": "@nx/angular", 269 | "name": "update-20-2-0-update-with-module-federation-import" 270 | }, 271 | { 272 | "cli": "nx", 273 | "version": "20.2.0-beta.5", 274 | "requires": { "@angular/core": ">=19.0.0" }, 275 | "description": "Update the @angular/cli package version to ~19.0.0.", 276 | "factory": "./src/migrations/update-20-2-0/update-angular-cli", 277 | "package": "@nx/angular", 278 | "name": "update-angular-cli-version-19-0-0" 279 | }, 280 | { 281 | "cli": "nx", 282 | "version": "20.2.0-beta.5", 283 | "requires": { "@angular/core": ">=19.0.0" }, 284 | "description": "Add the '@angular/localize/init' polyfill to the 'polyfills' option of targets using esbuild-based executors.", 285 | "factory": "./src/migrations/update-20-2-0/add-localize-polyfill-to-targets", 286 | "package": "@nx/angular", 287 | "name": "add-localize-polyfill-to-targets" 288 | }, 289 | { 290 | "cli": "nx", 291 | "version": "20.2.0-beta.5", 292 | "requires": { "@angular/core": ">=19.0.0" }, 293 | "description": "Update '@angular/ssr' import paths to use the new '/node' entry point when 'CommonEngine' is detected.", 294 | "factory": "./src/migrations/update-20-2-0/update-angular-ssr-imports-to-use-node-entry-point", 295 | "package": "@nx/angular", 296 | "name": "update-angular-ssr-imports-to-use-node-entry-point" 297 | }, 298 | { 299 | "cli": "nx", 300 | "version": "20.2.0-beta.6", 301 | "requires": { "@angular/core": ">=19.0.0" }, 302 | "description": "Disable the Angular ESLint prefer-standalone rule if not set.", 303 | "factory": "./src/migrations/update-20-2-0/disable-angular-eslint-prefer-standalone", 304 | "package": "@nx/angular", 305 | "name": "disable-angular-eslint-prefer-standalone" 306 | }, 307 | { 308 | "cli": "nx", 309 | "version": "20.2.0-beta.8", 310 | "requires": { "@angular/core": ">=19.0.0" }, 311 | "description": "Remove Angular ESLint rules that were removed in v19.0.0.", 312 | "factory": "./src/migrations/update-20-2-0/remove-angular-eslint-rules", 313 | "package": "@nx/angular", 314 | "name": "remove-angular-eslint-rules" 315 | }, 316 | { 317 | "cli": "nx", 318 | "version": "20.2.0-beta.8", 319 | "requires": { "@angular/core": ">=19.0.0" }, 320 | "description": "Remove the deprecated 'tailwindConfig' option from ng-packagr executors. Tailwind CSS configurations located at the project or workspace root will be picked up automatically.", 321 | "factory": "./src/migrations/update-20-2-0/remove-tailwind-config-from-ng-packagr-executors", 322 | "package": "@nx/angular", 323 | "name": "remove-tailwind-config-from-ng-packagr-executors" 324 | }, 325 | { 326 | "cli": "nx", 327 | "version": "20.3.0-beta.2", 328 | "description": "If workspace includes Module Federation projects, ensure the new @nx/module-federation package is installed.", 329 | "factory": "./src/migrations/update-20-3-0/ensure-nx-module-federation-package", 330 | "package": "@nx/angular", 331 | "name": "ensure-nx-module-federation-package" 332 | }, 333 | { 334 | "cli": "nx", 335 | "version": "20.4.0-beta.1", 336 | "requires": { "@angular/core": ">=19.1.0" }, 337 | "description": "Update the @angular/cli package version to ~19.1.0.", 338 | "factory": "./src/migrations/update-20-4-0/update-angular-cli", 339 | "package": "@nx/angular", 340 | "name": "update-angular-cli-version-19-1-0" 341 | }, 342 | { 343 | "description": "As of NgRx v18, the `TypedAction` has been removed in favor of `Action`.", 344 | "version": "18-beta", 345 | "factory": "./18_0_0-beta/index", 346 | "package": "@ngrx/store", 347 | "name": "ngrx-store-migration-18-beta" 348 | }, 349 | { 350 | "version": "19.0.0", 351 | "description": "Updates non-standalone Directives, Component and Pipes to 'standalone:false' and removes 'standalone:true' from those who are standalone", 352 | "factory": "./bundles/explicit-standalone-flag#migrate", 353 | "package": "@angular/core", 354 | "name": "explicit-standalone-flag" 355 | }, 356 | { 357 | "version": "19.0.0", 358 | "description": "Updates ExperimentalPendingTasks to PendingTasks", 359 | "factory": "./bundles/pending-tasks#migrate", 360 | "package": "@angular/core", 361 | "name": "pending-tasks" 362 | }, 363 | { 364 | "version": "19.0.0", 365 | "description": "Replaces `APP_INITIALIZER`, `ENVIRONMENT_INITIALIZER` & `PLATFORM_INITIALIZER` respectively with `provideAppInitializer`, `provideEnvironmentInitializer` & `providePlatformInitializer`.", 366 | "factory": "./bundles/provide-initializer#migrate", 367 | "optional": true, 368 | "package": "@angular/core", 369 | "name": "provide-initializer" 370 | }, 371 | { 372 | "version": "19.0.0-0", 373 | "description": "Updates Angular Material to v19", 374 | "factory": "./ng-update/index_bundled#updateToV19", 375 | "package": "@angular/material", 376 | "name": "migration-v19" 377 | }, 378 | { 379 | "version": "19.0.0-0", 380 | "description": "Updates the Angular CDK to v19", 381 | "factory": "./ng-update/index#updateToV19", 382 | "package": "@angular/cdk", 383 | "name": "migration-v19" 384 | }, 385 | { 386 | "description": "As of NgRx v18, the `tapResponse` import has been removed from `@ngrx/component-store` in favor of the `@ngrx/operators` package.", 387 | "version": "18-beta", 388 | "factory": "./18_0_0-beta/index", 389 | "package": "@ngrx/component-store", 390 | "name": "ngrx-component-store-migration-18-beta" 391 | }, 392 | { 393 | "description": "As of NgRx v18, the `concatLatestFrom` import has been removed from `@ngrx/effects` in favor of the `@ngrx/operators` package.", 394 | "version": "18-beta", 395 | "factory": "./18_0_0-beta/index", 396 | "package": "@ngrx/effects", 397 | "name": "ngrx-effects-migration-18-beta" 398 | }, 399 | { 400 | "description": "Make the state of all existing SignalStores unprotected", 401 | "version": "18.0.0-rc.3", 402 | "factory": "./18_0_0-rc_3-protected-state/index", 403 | "package": "@ngrx/signals", 404 | "name": "18_0_0-rc_3-protected-state" 405 | }, 406 | { 407 | "description": "Replace StateSignal usages with WritableStateSource", 408 | "version": "18.0.0-rc.3", 409 | "factory": "./18_0_0-rc_3-writablestatesource/index", 410 | "package": "@ngrx/signals", 411 | "name": "18_0_0-rc_3-writablestatesource" 412 | }, 413 | { 414 | "description": "Replace several properties with a single props object", 415 | "version": "19.0.0-rc.0", 416 | "factory": "./19_0_0-rc_0-props/index", 417 | "package": "@ngrx/signals", 418 | "name": "19_0_0-rc_0-props" 419 | } 420 | ] 421 | } 422 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultProject": "flights", 3 | "namedInputs": { 4 | "sharedGlobals": [], 5 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 6 | "production": [ 7 | "default", 8 | "!{projectRoot}/tsconfig.spec.json", 9 | "!{projectRoot}/**/*.spec.[jt]s", 10 | "!{projectRoot}/karma.conf.js", 11 | "!{projectRoot}/.eslintrc.json", 12 | "!{projectRoot}/eslint.config.js", 13 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 14 | "!{projectRoot}/jest.config.[jt]s", 15 | "!{projectRoot}/src/test-setup.[jt]s", 16 | "!{projectRoot}/test-setup.[jt]s" 17 | ] 18 | }, 19 | "targetDefaults": { 20 | "build": { 21 | "dependsOn": ["^build"], 22 | "inputs": ["production", "^production"], 23 | "cache": true 24 | }, 25 | "test": { 26 | "inputs": [ 27 | "default", 28 | "^production", 29 | "{workspaceRoot}/karma.conf.js", 30 | "{workspaceRoot}/jest.preset.js" 31 | ], 32 | "cache": true 33 | }, 34 | "@nx/jest:jest": { 35 | "cache": true, 36 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 37 | "options": { 38 | "passWithNoTests": true 39 | }, 40 | "configurations": { 41 | "ci": { 42 | "ci": true, 43 | "codeCoverage": true 44 | } 45 | } 46 | }, 47 | "@nx/eslint:lint": { 48 | "inputs": [ 49 | "default", 50 | "{workspaceRoot}/.eslintrc.json", 51 | "{workspaceRoot}/eslint.config.js" 52 | ], 53 | "cache": true 54 | } 55 | }, 56 | "generators": { 57 | "@nx/angular:application": { 58 | "style": "css", 59 | "linter": "eslint", 60 | "unitTestRunner": "jest", 61 | "e2eTestRunner": "none", 62 | "projectNameAndRootFormat": "derived" 63 | }, 64 | "@nx/angular:library": { 65 | "linter": "eslint", 66 | "unitTestRunner": "jest", 67 | "projectNameAndRootFormat": "derived" 68 | }, 69 | "@nx/angular:component": { 70 | "style": "css" 71 | } 72 | }, 73 | "useInferencePlugins": false, 74 | "defaultBase": "lab500-enterprise-starter", 75 | "useLegacyCache": false 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flight-demo/source", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "nx", 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "watch": "nx build --watch --configuration development", 9 | "test": "nx test", 10 | "prepare": "husky", 11 | "lint": "nx lint", 12 | "run:all": "node node_modules/@angular-architects/module-federation/src/server/mf-dev-server.js" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular-architects/module-federation": "^19.0.0", 17 | "@angular-architects/module-federation-tools": "^19.0.0", 18 | "@angular-architects/native-federation": "^19.0.0", 19 | "@angular-architects/ngrx-toolkit": "^19.1.0", 20 | "@angular-architects/paper-design": "^1.0.3", 21 | "@angular/animations": "19.1.8", 22 | "@angular/cdk": "19.1.5", 23 | "@angular/common": "19.1.8", 24 | "@angular/compiler": "19.1.8", 25 | "@angular/core": "19.1.8", 26 | "@angular/forms": "19.1.8", 27 | "@angular/material": "19.1.5", 28 | "@angular/platform-browser": "19.1.8", 29 | "@angular/platform-browser-dynamic": "19.1.8", 30 | "@angular/router": "19.1.8", 31 | "@ngrx/component": "19.0.1", 32 | "@ngrx/component-store": "19.0.1", 33 | "@ngrx/effects": "19.0.1", 34 | "@ngrx/entity": "19.0.1", 35 | "@ngrx/operators": "^18.0.0", 36 | "@ngrx/router-store": "19.0.1", 37 | "@ngrx/signals": "19.0.1", 38 | "@ngrx/store": "19.0.1", 39 | "@ngrx/store-devtools": "19.0.1", 40 | "@nx/angular": "20.4.6", 41 | "@softarc/detective": "^1.2.3", 42 | "date-fns": "^2.30.0", 43 | "es-module-shims": "^1.5.12", 44 | "rxjs": "7.8.1", 45 | "tslib": "^2.5.2", 46 | "zone.js": "0.15.0" 47 | }, 48 | "devDependencies": { 49 | "@angular-architects/ddd": "^19.0.0", 50 | "@angular-devkit/build-angular": "19.1.9", 51 | "@angular-devkit/core": "19.1.9", 52 | "@angular-devkit/schematics": "19.1.9", 53 | "@angular-eslint/eslint-plugin": "19.1.0", 54 | "@angular-eslint/eslint-plugin-template": "19.1.0", 55 | "@angular-eslint/template-parser": "19.1.0", 56 | "@angular/cli": "~19.1.0", 57 | "@angular/compiler-cli": "19.1.8", 58 | "@angular/language-service": "19.1.8", 59 | "@ngrx/schematics": "19.0.1", 60 | "@nx/cypress": "20.4.6", 61 | "@nx/eslint": "20.4.6", 62 | "@nx/eslint-plugin": "20.4.6", 63 | "@nx/jest": "20.4.6", 64 | "@nx/js": "20.4.6", 65 | "@nx/web": "20.4.6", 66 | "@nx/workspace": "20.4.6", 67 | "@schematics/angular": "19.1.9", 68 | "@softarc/eslint-plugin-sheriff": "^0.18.0", 69 | "@softarc/sheriff-core": "^0.18.0", 70 | "@swc-node/register": "1.9.2", 71 | "@swc/core": "1.5.7", 72 | "@swc/helpers": "0.5.15", 73 | "@types/jasmine": "~4.3.1", 74 | "@types/jest": "29.5.14", 75 | "@types/node": "18.16.9", 76 | "@typescript-eslint/eslint-plugin": "^8.26.0", 77 | "@typescript-eslint/parser": "8.26.0", 78 | "@typescript-eslint/utils": "8.26.0", 79 | "cypress": "13.17.0", 80 | "eslint": "8.57.1", 81 | "eslint-config-prettier": "9.0.0", 82 | "eslint-plugin-cypress": "2.13.4", 83 | "husky": "^9.1.7", 84 | "jasmine-core": "^4.0.0", 85 | "jest": "29.7.0", 86 | "jest-environment-jsdom": "29.7.0", 87 | "jest-preset-angular": "14.4.2", 88 | "jsonc-eslint-parser": "^2.1.0", 89 | "karma": "~6.4.2", 90 | "karma-chrome-launcher": "~3.2.0", 91 | "karma-coverage": "~2.2.0", 92 | "karma-jasmine": "~5.1.0", 93 | "karma-jasmine-html-reporter": "~2.0.0", 94 | "ngx-build-plus": "^17.0.0", 95 | "nx": "20.4.6", 96 | "prettier": "^2.8.8", 97 | "pretty-quick": "^3.1.3", 98 | "ts-jest": "^29.1.0", 99 | "ts-node": "10.9.1", 100 | "typescript": "5.7.3" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /sheriff.config.ts: -------------------------------------------------------------------------------- 1 | import { noDependencies, sameTag, SheriffConfig } from '@softarc/sheriff-core'; 2 | 3 | export const sheriffConfig: SheriffConfig = { 4 | version: 1, 5 | 6 | tagging: { 7 | libs: { 8 | 'ui-/src': ['domain:shared', 'type:ui', 'type:data'], 9 | 'util-/src': ['domain:shared', 'type:util'], 10 | }, 11 | 'apps//src/app': { 12 | 'domains/': { 13 | 'feature-': ['domain:', 'type:feature'], 14 | 'ui-': ['domain:', 'type:ui'], 15 | data: ['domain:', 'type:data'], 16 | 'util-': ['domain:', 'type:util'], 17 | }, 18 | }, 19 | }, 20 | depRules: { 21 | root: ['*'], 22 | 23 | 'domain:*': [sameTag, 'domain:shared'], 24 | 25 | 'type:feature': ['type:ui', 'type:data', 'type:util'], 26 | 'type:ui': ['type:data', 'type:util'], 27 | 'type:data': ['type:util'], 28 | 'type:util': noDependencies, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /tools/generators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manfredsteyer/lightweight-state/c2a628d7e5c063ac8465ac9a75988089662376c6/tools/generators/.gitkeep -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "declaration": false, 14 | "downlevelIteration": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022", 20 | "useDefineForClassFields": false, 21 | "lib": ["ES2022", "dom"], 22 | "paths": { 23 | "@demo/*": ["apps/flights/src/app/domains/*"], 24 | "@flight-demo/ui-common": ["libs/ui-common/src/index.ts"] 25 | }, 26 | "rootDir": "." 27 | }, 28 | "angularCompilerOptions": { 29 | "enableI18nLegacyMessageIdFormat": false, 30 | "strictInjectionParameters": true, 31 | "strictInputAccessModifiers": true, 32 | "strictTemplates": true 33 | }, 34 | "exclude": ["node_modules", "tmp"] 35 | } 36 | --------------------------------------------------------------------------------