diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..59d9a3a
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,16 @@
+# Editor configuration, see https://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.ts]
+quote_type = single
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
diff --git a/angular.json b/angular.json
index b8acc35..1edc6ea 100644
--- a/angular.json
+++ b/angular.json
@@ -47,18 +47,19 @@
"src/favicon.ico"
],
"styles": [
- "@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.scss"
],
- "scripts": []
+ "scripts": [
+ "node_modules/flowbite/dist/flowbite.min.js"
+ ]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
- "maximumWarning": "800kb",
- "maximumError": "2mb"
+ "maximumWarning": "5mb",
+ "maximumError": "10mb"
},
{
"type": "anyComponentStyle",
@@ -69,9 +70,7 @@
"outputHashing": "all"
},
"development": {
- "buildOptimizer": false,
"optimization": false,
- "vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
@@ -83,10 +82,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
- "browserTarget": "template:build:production"
+ "buildTarget": "template:build:production"
},
"development": {
- "browserTarget": "template:build:development"
+ "buildTarget": "template:build:development"
}
},
"defaultConfiguration": "development"
@@ -94,7 +93,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
- "browserTarget": "template:build"
+ "buildTarget": "template:build"
}
},
"test": {
@@ -110,7 +109,6 @@
"src/favicon.ico"
],
"styles": [
- "@angular/material/prebuilt-themes/deeppurple-amber.scss",
"src/styles.scss"
],
"scripts": []
diff --git a/bin/script/firstRun b/bin/script/firstRun
new file mode 100755
index 0000000..608a1a8
--- /dev/null
+++ b/bin/script/firstRun
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
+ENV_DIR=$(realpath $PROJECT_DIR/../../../)
+
+source $ENV_DIR/bin/denv_msg
+source $ENV_DIR/bin/drun
+
+# Install node Packages
+denv_echo_msg "[frontend]: NPM install"
+drun frontend npm install
+drun frontend npm install -g @angular/cli
+denv_success_msg "[frontend]: NPM install done"
+
+# Initial build of website
+denv_echo_msg "[frontend]: NPM run build"
+drun frontend npm run build
+denv_success_msg "[frontend]: NPM run build done"
\ No newline at end of file
diff --git a/bin/script/init b/bin/script/init
index 8f28c30..80a3818 100755
--- a/bin/script/init
+++ b/bin/script/init
@@ -4,40 +4,10 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
-EXIT=0
+source $ENV_DIR/bin/denv_msg
# Check docker-compose.yml file
-if [ ! -f "$PROJECT_DIR/docker/docker-compose.yml" ]
-then
+if [ ! -f "$PROJECT_DIR/docker/docker-compose.yml" ] ; then
cp "$PROJECT_DIR/docker/docker-compose.yml.dist" "$PROJECT_DIR/docker/docker-compose.yml"
- EXIT=1
-fi
-
-# Check docker-compose-mac.yml file
-if [ ! -f "$PROJECT_DIR/docker/docker-compose-mac.yml" ]
-then
- cp "$PROJECT_DIR/docker/docker-compose-mac.yml.dist" "$PROJECT_DIR/docker/docker-compose-mac.yml"
- EXIT=1
-fi
-
-
-if [ $EXIT -eq 1 ]
-then
- echo "docker-compose or env files created, please change variables and call init again"
- exit 1
-fi
-
-# Source key-scripts
-source $ENV_DIR/bin/drun
-source $ENV_DIR/bin/dexec
-
-# Build and start docker containers
-dexec template-frontend build
-dexec template-frontend up -d
-
-# Install node Packages
-drun template-frontend npm install
-drun template-frontend npm install -g @angular/cli
-
-# Initial build of website
-drun template-frontend npm run build
\ No newline at end of file
+ denv_info_msg "[frontend] Created docker-compose.yml"
+fi
\ No newline at end of file
diff --git a/bin/script/update b/bin/script/update
new file mode 100755
index 0000000..931daaf
--- /dev/null
+++ b/bin/script/update
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
+ENV_DIR=$(realpath $PROJECT_DIR/../../../)
+CWD=$(pwd)
+
+source $ENV_DIR/bin/denv_msg
+source $ENV_DIR/bin/drun
+
+
+# Pull branch in project directory
+denv_echo_msg "[frontend]: Git pull"
+cd "$PROJECT_DIR"
+git pull
+denv_success_msg "[frontend]: Git pull done"
+
+# Install node Packages
+denv_echo_msg "[frontend]: NPM install"
+drun frontend npm install
+denv_success_msg "[frontend]: NPM install done"
+
+# Initial build of website
+denv_echo_msg "[frontend]: NPM run build"
+drun frontend npm run build
+denv_success_msg "[frontend]: NPM run build done"
+
+# Switch back to current working directory
+cd "$CWD"
\ No newline at end of file
diff --git a/docker/docker-compose-mac.yml.dist b/docker/docker-compose-mac.yml.dist
deleted file mode 100644
index 07d9979..0000000
--- a/docker/docker-compose-mac.yml.dist
+++ /dev/null
@@ -1,31 +0,0 @@
-networks:
- template:
- external: true
-
-services:
- template-frontend-app:
- image: template-frontend-app
- networks:
- - template
- volumes:
- - /Users/flo/dev/frontend/template/:/var/www/
- build:
- context: ./../
- dockerfile: ./docker/npm/dockerfile
- tty: true
-
- template-frontend-nginx:
- image: template-frontend-nginx
- networks:
- - template
- volumes:
- - /Users/flo/dev/frontend/template/:/var/www/html:z
- build:
- context: ./../
- dockerfile: ./docker/nginx/dockerfile
- labels:
- - "traefik.http.routers.frontend.rule=Host(`template.local`)"
- - "traefik.http.routers.frontend.entrypoints=websecure"
- - "traefik.http.routers.frontend.tls.certresolver=le"
- depends_on:
- - template-frontend-app
\ No newline at end of file
diff --git a/docker/docker-compose.yml.dist b/docker/docker-compose.yml.dist
index 5dd392e..5b80811 100644
--- a/docker/docker-compose.yml.dist
+++ b/docker/docker-compose.yml.dist
@@ -28,4 +28,4 @@ services:
- "traefik.http.routers.frontend.entrypoints=websecure"
- "traefik.http.routers.frontend.tls.certresolver=le"
depends_on:
- - template-frontend-app
\ No newline at end of file
+ - template-frontend-app
diff --git a/package.json b/package.json
index 24e8935..233aa94 100644
--- a/package.json
+++ b/package.json
@@ -9,24 +9,28 @@
},
"private": true,
"dependencies": {
- "@angular/animations": "^15.2.10",
- "@angular/cdk": "^15.2.9",
- "@angular/common": "^15.2.0",
- "@angular/compiler": "^15.2.0",
- "@angular/core": "^15.2.0",
- "@angular/forms": "^15.2.0",
- "@angular/material": "^15.2.9",
- "@angular/platform-browser": "^15.2.0",
- "@angular/platform-browser-dynamic": "^15.2.0",
- "@angular/router": "^15.2.0",
+ "@angular/animations": "^19.0.4",
+ "@angular/common": "^19.0.4",
+ "@angular/compiler": "^19.0.4",
+ "@angular/core": "^19.0.4",
+ "@angular/forms": "^19.0.4",
+ "@angular/material": "^18.2.4",
+ "@angular/platform-browser": "^19.0.4",
+ "@angular/platform-browser-dynamic": "^19.0.4",
+ "@angular/router": "^19.0.4",
+ "@ngx-translate/core": "^15.0.0",
+ "@ngx-translate/http-loader": "^8.0.0",
+ "angular-svg-icon": "^18.0.2",
+ "apexcharts": "^4.0.0",
+ "flowbite": "^2.5.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
- "zone.js": "~0.12.0"
+ "zone.js": "~0.15.0"
},
"devDependencies": {
- "@angular-devkit/build-angular": "^15.2.6",
- "@angular/cli": "~15.2.6",
- "@angular/compiler-cli": "^15.2.0",
+ "@angular-devkit/build-angular": "^19.0.5",
+ "@angular/cli": "~19.0.5",
+ "@angular/compiler-cli": "^19.0.4",
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
@@ -34,7 +38,7 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
- "tailwindcss": "^3.4.3",
- "typescript": "~4.9.4"
+ "tailwindcss": "^3.4.10",
+ "typescript": "~5.6.3"
}
}
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 0680b43..1363652 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1 +1,5 @@
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index fe8da85..6e2d87d 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,9 +1,59 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit } from "@angular/core";
+import { AuthService } from "./core/services/auth.service";
+import { TranslateService } from "@ngx-translate/core";
@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.scss']
+ selector: "app-root",
+ templateUrl: "./app.component.html",
+ styleUrls: ["./app.component.scss"],
+ standalone: false
})
-export class AppComponent {
+export class AppComponent implements OnInit {
+ constructor(
+ private authService: AuthService,
+ private translate: TranslateService
+ ) {
+ var language = navigator.language;
+
+ switch(language) {
+ case "en":
+ case "en_EN":
+ case "en-EN":
+ language = 'en';
+ break;
+ case "de":
+ case "de_DE":
+ case "de-DE":
+ language = 'de';
+ break;
+ case "es":
+ case "es_ES":
+ case "es-ES":
+ language = 'es';
+ break;
+ case "fr":
+ case "fr_FR":
+ case "fr-FR":
+ language = 'fr';
+ break;
+ case "tr":
+ case "tr_TR":
+ case "tr-TR":
+ language = 'tr';
+ break;
+ default:
+ language = 'en';
+ }
+
+ this.translate.setDefaultLang(language);
+ this.translate.use(language);
+
+ this.translate.onLangChange.subscribe(() => {
+ document.title = this.translate.instant("title");
+ });
+ }
+
+ ngOnInit(): void {
+ this.authService.readUserState();
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ad51b68..2e9badd 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,30 +1,56 @@
-import { NgModule } from '@angular/core';
-import { BrowserModule } from '@angular/platform-browser';
-import { HttpClientModule } from '@angular/common/http';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-
-import { AppComponent } from './app.component';
-import { RouterModule, Routes } from '@angular/router';
-import { SharedModule } from './shared/shared.module';
-import { HomeComponent } from './core/components/home/home.component';
-import { CoreModule } from './core/core.module';
+import { NgModule } from "@angular/core";
+import { BrowserModule } from "@angular/platform-browser";
+import { HttpClient, provideHttpClient, withInterceptorsFromDi } from "@angular/common/http";
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { TranslateModule, TranslateLoader, TranslatePipe } from "@ngx-translate/core";
+import { TranslateHttpLoader } from "@ngx-translate/http-loader";
+import { AppComponent } from "./app.component";
+import { RouterModule, Routes } from "@angular/router";
+import { SharedModule } from "./shared/shared.module";
+import { CoreModule } from "./core/core.module";
+import { AngularSvgIconModule } from 'angular-svg-icon';
+import { DatePipe } from "@angular/common";
const routes: Routes = [
- { path: 'home', component: HomeComponent },
- { path: '', redirectTo: 'home', pathMatch: 'full' },
+ {
+ path: "auth",
+ loadChildren: () =>
+ import("./core/auth/auth.module").then((m) => m.AuthModule),
+ },
+ {
+ path: "",
+ loadChildren: () =>
+ import("./core/home/home.module").then((m) => m.HomeModule),
+ }
];
+export function createTranslateLoader(http: HttpClient) {
+ return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
+}
+
@NgModule({
declarations: [AppComponent],
+ bootstrap: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
- HttpClientModule,
+ RouterModule,
RouterModule.forRoot(routes),
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useFactory: createTranslateLoader,
+ deps: [HttpClient],
+ },
+ }),
+ AngularSvgIconModule.forRoot(),
SharedModule,
- CoreModule,
+ CoreModule
+ ],
+ providers: [
+ provideHttpClient(withInterceptorsFromDi()),
+ TranslatePipe,
+ DatePipe
],
- providers: [],
- bootstrap: [AppComponent],
})
export class AppModule {}
diff --git a/src/app/app.service.ts b/src/app/app.service.ts
deleted file mode 100644
index e65248d..0000000
--- a/src/app/app.service.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Injectable } from '@angular/core';
-
-@Injectable({
- providedIn: 'root'
-})
-export class AppService {
-
- private username: string|null;
-
- constructor() {
- this.username = null;
- }
-
- public getUsername(): string|null {
- return this.username;
- }
-
- public setUsername(name: string|null): void {
- this.username = name;
- }
-}
diff --git a/src/app/core/auth/auth.module.ts b/src/app/core/auth/auth.module.ts
new file mode 100644
index 0000000..e126ed9
--- /dev/null
+++ b/src/app/core/auth/auth.module.ts
@@ -0,0 +1,57 @@
+import { NgModule } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { LoginComponent } from "./components/login/login.component";
+import { RegistrationComponent } from "./components/registration/registration.component";
+import { ConfirmRegistrationComponent } from "./components/confirm-registration/confirm-registration.component";
+import { RouterModule, Routes } from "@angular/router";
+import { ReactiveFormsModule } from "@angular/forms";
+import { ForgotPasswordComponent } from "./components/forgot-password/forgot-password.component";
+import { ResetPasswordComponent } from "./components/reset-password/reset-password.component";
+import { TranslateModule } from "@ngx-translate/core";
+import { SharedModule } from "src/app/shared/shared.module";
+import { CoreModule } from "../core.module";
+import { AuthComponent } from "./components/auth/auth.component";
+import { AngularSvgIconModule } from "angular-svg-icon";
+
+const routes: Routes = [
+ {
+ path: "",
+ component: AuthComponent,
+ children: [
+ { path: "login", component: LoginComponent },
+ { path: "registration", component: RegistrationComponent },
+ { path: "forgot-password", component: ForgotPasswordComponent },
+ { path: "registration/:registrationId", component: ConfirmRegistrationComponent },
+ { path: "reset-password/:passwordToken", component: ResetPasswordComponent },
+ { path: "**", redirectTo: "login" },
+ ]
+ }
+];
+
+@NgModule({
+ declarations: [
+ LoginComponent,
+ RegistrationComponent,
+ ConfirmRegistrationComponent,
+ ForgotPasswordComponent,
+ ResetPasswordComponent,
+ AuthComponent,
+ ],
+ exports: [
+ LoginComponent,
+ RegistrationComponent,
+ ConfirmRegistrationComponent,
+ ForgotPasswordComponent,
+ ResetPasswordComponent,
+ ],
+ imports: [
+ RouterModule.forChild(routes),
+ AngularSvgIconModule,
+ CommonModule,
+ ReactiveFormsModule,
+ TranslateModule,
+ SharedModule,
+ CoreModule
+ ],
+})
+export class AuthModule {}
diff --git a/src/app/core/auth/components/auth/auth.component.html b/src/app/core/auth/components/auth/auth.component.html
new file mode 100644
index 0000000..1e59a05
--- /dev/null
+++ b/src/app/core/auth/components/auth/auth.component.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
{{ "title" | translate }}
+ {{ label | translate }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/core/components/home/home.component.scss b/src/app/core/auth/components/auth/auth.component.scss
similarity index 100%
rename from src/app/core/components/home/home.component.scss
rename to src/app/core/auth/components/auth/auth.component.scss
diff --git a/src/app/core/auth/components/auth/auth.component.ts b/src/app/core/auth/components/auth/auth.component.ts
new file mode 100644
index 0000000..0418628
--- /dev/null
+++ b/src/app/core/auth/components/auth/auth.component.ts
@@ -0,0 +1,44 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute, NavigationEnd, Router } from "@angular/router";
+import { initFlowbite } from "flowbite";
+import { filter, map } from "rxjs";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-auth",
+ templateUrl: "./auth.component.html",
+ styleUrls: ["./auth.component.scss"],
+ standalone: false
+})
+export class AuthComponent implements OnInit {
+ label: string = '';
+
+ constructor(
+ private authService: AuthService,
+ private route: ActivatedRoute,
+ private router: Router
+ ) {
+ this.router.events
+ .pipe(
+ filter(event => event instanceof NavigationEnd),
+ map(() => this.route)
+ ).subscribe(newRoute => {
+ if ((newRoute.firstChild?.routeConfig?.path ?? undefined) === undefined) {
+ this.label = '';
+ } else {
+ let routePart = newRoute.firstChild!.routeConfig!.path!;
+ let routeParts = routePart.split('/');
+ this.label = 'auth.' + routeParts[0];
+ }
+ });
+
+ this.authService.readUserState().subscribe(response => {
+ this.authService.currentState$.next(response);
+ this.router.navigateByUrl("/dashboard");
+ });
+ }
+
+ ngOnInit(): void {
+ initFlowbite();
+ }
+}
diff --git a/src/app/core/auth/components/confirm-registration/confirm-registration.component.html b/src/app/core/auth/components/confirm-registration/confirm-registration.component.html
new file mode 100644
index 0000000..49d78cc
--- /dev/null
+++ b/src/app/core/auth/components/confirm-registration/confirm-registration.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/core/components/navigation/navigation.component.scss b/src/app/core/auth/components/confirm-registration/confirm-registration.component.scss
similarity index 100%
rename from src/app/core/components/navigation/navigation.component.scss
rename to src/app/core/auth/components/confirm-registration/confirm-registration.component.scss
diff --git a/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts b/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts
new file mode 100644
index 0000000..1753f09
--- /dev/null
+++ b/src/app/core/auth/components/confirm-registration/confirm-registration.component.ts
@@ -0,0 +1,42 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { ActivatedRoute, Router } from "@angular/router";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-confirm-registration",
+ templateUrl: "./confirm-registration.component.html",
+ styleUrls: ["./confirm-registration.component.scss"],
+ standalone: false
+})
+export class ConfirmRegistrationComponent {
+ confirmRegistrationForm = new FormGroup({
+ password: new FormControl("", [Validators.required]),
+ passwordConfirmation: new FormControl("", [Validators.required]),
+ });
+
+ registrationId: string | undefined;
+
+ constructor(
+ private authService: AuthService,
+ private activatedRoute: ActivatedRoute,
+ private router: Router
+ ) {
+ this.activatedRoute.params.subscribe((params) => {
+ this.registrationId = params["registrationId"];
+ });
+ }
+
+ confirm(): void {
+ if (this.registrationId === undefined) return;
+
+ this.authService.confirmRegistration({
+ id: this.registrationId!,
+ password: this.confirmRegistrationForm.value.password!,
+ passwordConfirmation:
+ this.confirmRegistrationForm.value.passwordConfirmation!,
+ }).subscribe(response => {
+ this.router.navigateByUrl("/auth");
+ });
+ }
+}
diff --git a/src/app/core/auth/components/forgot-password/forgot-password.component.html b/src/app/core/auth/components/forgot-password/forgot-password.component.html
new file mode 100644
index 0000000..fd81a55
--- /dev/null
+++ b/src/app/core/auth/components/forgot-password/forgot-password.component.html
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/form/form.component.scss b/src/app/core/auth/components/forgot-password/forgot-password.component.scss
similarity index 100%
rename from src/app/shared/components/form/form.component.scss
rename to src/app/core/auth/components/forgot-password/forgot-password.component.scss
diff --git a/src/app/core/auth/components/forgot-password/forgot-password.component.ts b/src/app/core/auth/components/forgot-password/forgot-password.component.ts
new file mode 100644
index 0000000..cf19bcc
--- /dev/null
+++ b/src/app/core/auth/components/forgot-password/forgot-password.component.ts
@@ -0,0 +1,27 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { Router } from "@angular/router";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-forgot-password",
+ templateUrl: "./forgot-password.component.html",
+ styleUrls: ["./forgot-password.component.scss"],
+ standalone: false
+})
+export class ForgotPasswordComponent {
+ forgotPasswordForm = new FormGroup({
+ mail: new FormControl("", [Validators.required, Validators.email]),
+ });
+
+ constructor(private authService: AuthService, private router: Router) {
+ }
+
+ confirm(): void {
+ this.authService.forgotPassword({
+ mail: this.forgotPasswordForm.value.mail!,
+ }).subscribe(response => {
+ this.router.navigateByUrl("/auth");
+ });
+ }
+}
diff --git a/src/app/core/auth/components/login/login.component.html b/src/app/core/auth/components/login/login.component.html
new file mode 100644
index 0000000..85bb028
--- /dev/null
+++ b/src/app/core/auth/components/login/login.component.html
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/tab-control/tab-control.component.scss b/src/app/core/auth/components/login/login.component.scss
similarity index 100%
rename from src/app/shared/components/tab-control/tab-control.component.scss
rename to src/app/core/auth/components/login/login.component.scss
diff --git a/src/app/core/auth/components/login/login.component.ts b/src/app/core/auth/components/login/login.component.ts
new file mode 100644
index 0000000..0a18f17
--- /dev/null
+++ b/src/app/core/auth/components/login/login.component.ts
@@ -0,0 +1,29 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { Router } from "@angular/router";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-login",
+ templateUrl: "./login.component.html",
+ styleUrls: ["./login.component.scss"],
+ standalone: false
+})
+export class LoginComponent {
+ loginForm = new FormGroup({
+ identifier: new FormControl("", [Validators.required]),
+ password: new FormControl("", [Validators.required]),
+ });
+
+ constructor(private authService: AuthService, private router: Router) {
+ }
+
+ login(): void {
+ this.authService.login({
+ identifier: this.loginForm.value.identifier!,
+ password: this.loginForm.value.password!,
+ }).subscribe(response => {
+ this.router.navigateByUrl("/dashboard");
+ });
+ }
+}
diff --git a/src/app/core/auth/components/registration/registration.component.html b/src/app/core/auth/components/registration/registration.component.html
new file mode 100644
index 0000000..6b70d5c
--- /dev/null
+++ b/src/app/core/auth/components/registration/registration.component.html
@@ -0,0 +1,17 @@
+
diff --git a/src/app/core/auth/components/registration/registration.component.scss b/src/app/core/auth/components/registration/registration.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/auth/components/registration/registration.component.ts b/src/app/core/auth/components/registration/registration.component.ts
new file mode 100644
index 0000000..1c4d447
--- /dev/null
+++ b/src/app/core/auth/components/registration/registration.component.ts
@@ -0,0 +1,29 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { Router } from "@angular/router";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-registration",
+ templateUrl: "./registration.component.html",
+ styleUrls: ["./registration.component.scss"],
+ standalone: false
+})
+export class RegistrationComponent {
+ registrationForm = new FormGroup({
+ mail: new FormControl("", [Validators.required]),
+ username: new FormControl("", [Validators.required]),
+ });
+
+ constructor(private authService: AuthService, private router: Router) {
+ }
+
+ register(): void {
+ this.authService.register({
+ mail: this.registrationForm.value.mail!,
+ username: this.registrationForm.value.username!,
+ }).subscribe(response => {
+ this.router.navigateByUrl("/auth");
+ });
+ }
+}
diff --git a/src/app/core/auth/components/reset-password/reset-password.component.html b/src/app/core/auth/components/reset-password/reset-password.component.html
new file mode 100644
index 0000000..24c565d
--- /dev/null
+++ b/src/app/core/auth/components/reset-password/reset-password.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/core/auth/components/reset-password/reset-password.component.scss b/src/app/core/auth/components/reset-password/reset-password.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/auth/components/reset-password/reset-password.component.ts b/src/app/core/auth/components/reset-password/reset-password.component.ts
new file mode 100644
index 0000000..09988d7
--- /dev/null
+++ b/src/app/core/auth/components/reset-password/reset-password.component.ts
@@ -0,0 +1,43 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { ActivatedRoute, Router } from "@angular/router";
+import { initFlowbite } from "flowbite";
+import { filter } from "rxjs";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-reset-password",
+ templateUrl: "./reset-password.component.html",
+ styleUrls: ["./reset-password.component.scss"],
+ standalone: false
+})
+export class ResetPasswordComponent {
+ resetPasswordForm = new FormGroup({
+ newPassword: new FormControl("", [Validators.required]),
+ passwordConfirmation: new FormControl("", [Validators.required]),
+ });
+
+ passwordToken: string | undefined;
+
+ constructor(
+ private authService: AuthService,
+ private activatedRoute: ActivatedRoute,
+ private router: Router
+ ) {
+ this.activatedRoute.params.subscribe((params) => {
+ this.passwordToken = params["passwordToken"];
+ });
+ }
+
+ confirm(): void {
+ if (this.passwordToken === undefined) return;
+
+ this.authService.resetPassword({
+ passwordToken: this.passwordToken!,
+ newPassword: this.resetPasswordForm.value.newPassword!,
+ passwordConfirmation: this.resetPasswordForm.value.passwordConfirmation!,
+ }).subscribe(response => {
+ this.router.navigateByUrl("/auth");
+ });
+ }
+}
diff --git a/src/app/core/components/home/home.component.html b/src/app/core/components/home/home.component.html
deleted file mode 100644
index bd16462..0000000
--- a/src/app/core/components/home/home.component.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
- Home
-
\ No newline at end of file
diff --git a/src/app/core/components/home/home.component.ts b/src/app/core/components/home/home.component.ts
deleted file mode 100644
index b1f2660..0000000
--- a/src/app/core/components/home/home.component.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Component } from '@angular/core';
-import { RequestService } from 'src/app/core/services/request.service';
-
-@Component({
- selector: 'app-home',
- templateUrl: './home.component.html',
- styleUrls: ['./home.component.scss'],
-})
-export class HomeComponent {
- constructor(public requestService: RequestService) {
- }
-}
diff --git a/src/app/core/components/language-picker/language-picker.component.html b/src/app/core/components/language-picker/language-picker.component.html
new file mode 100644
index 0000000..272ccb2
--- /dev/null
+++ b/src/app/core/components/language-picker/language-picker.component.html
@@ -0,0 +1,23 @@
+
+
diff --git a/src/app/core/components/language-picker/language-picker.component.scss b/src/app/core/components/language-picker/language-picker.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/components/language-picker/language-picker.component.ts b/src/app/core/components/language-picker/language-picker.component.ts
new file mode 100644
index 0000000..a11af0c
--- /dev/null
+++ b/src/app/core/components/language-picker/language-picker.component.ts
@@ -0,0 +1,67 @@
+import { Component, OnInit } from "@angular/core";
+import { initFlowbite } from "flowbite";
+import { AppService } from "../../services/app.service";
+import { TranslateService } from "@ngx-translate/core";
+
+interface Language {
+ imageSrc: string;
+ label: string;
+}
+
+@Component({
+ selector: "app-language-picker",
+ templateUrl: "./language-picker.component.html",
+ styleUrls: ["./language-picker.component.scss"],
+ standalone: false
+})
+export class LanguagePickerComponent implements OnInit {
+ languages: Language[] = [
+ {
+ label: "de",
+ imageSrc: "assets/de.svg"
+ },
+ {
+ label: "en",
+ imageSrc: "assets/en.svg"
+ },
+ {
+ label: "tr",
+ imageSrc: "assets/tr.svg"
+ },
+ {
+ label: "es",
+ imageSrc: "assets/es.svg"
+ },
+ {
+ label: "fr",
+ imageSrc: "assets/fr.svg"
+ },
+ {
+ label: "it",
+ imageSrc: "assets/it.svg"
+ },
+ {
+ label: "jp",
+ imageSrc: "assets/jp.svg"
+ },
+ ];
+ selectedLanguage: Language;
+
+ constructor(
+ private appService: AppService,
+ private translate: TranslateService,
+ ) {
+ this.selectedLanguage = this.languages.find((f) => {
+ f.label === translate.currentLang;
+ }) ?? this.languages[0];
+ }
+
+ ngOnInit(): void {
+ initFlowbite();
+ }
+
+ selectLanguage(language: Language): void {
+ this.translate.use(language.label);
+ this.selectedLanguage = language;
+ }
+}
diff --git a/src/app/core/components/navigation/navigation.component.html b/src/app/core/components/navigation/navigation.component.html
deleted file mode 100644
index a658057..0000000
--- a/src/app/core/components/navigation/navigation.component.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- Navigation
-
-
\ No newline at end of file
diff --git a/src/app/core/components/navigation/navigation.component.ts b/src/app/core/components/navigation/navigation.component.ts
deleted file mode 100644
index 6c2fd97..0000000
--- a/src/app/core/components/navigation/navigation.component.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'app-navigation',
- templateUrl: './navigation.component.html',
- styleUrls: ['./navigation.component.scss'],
-})
-export class NavigationComponent {
-}
diff --git a/src/app/core/components/notification-bar/notification-bar.component.html b/src/app/core/components/notification-bar/notification-bar.component.html
new file mode 100644
index 0000000..0278b0d
--- /dev/null
+++ b/src/app/core/components/notification-bar/notification-bar.component.html
@@ -0,0 +1,42 @@
+
+
+
{{ item.element.type }}
+
+ {{ item.element.message }}
+
+
+
diff --git a/src/app/core/components/notification-bar/notification-bar.component.scss b/src/app/core/components/notification-bar/notification-bar.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/components/notification-bar/notification-bar.component.ts b/src/app/core/components/notification-bar/notification-bar.component.ts
new file mode 100644
index 0000000..7695460
--- /dev/null
+++ b/src/app/core/components/notification-bar/notification-bar.component.ts
@@ -0,0 +1,70 @@
+import { Component } from "@angular/core";
+import { NotificationService } from "../../services/notification.service";
+
+export interface NotificationElement {
+ message: string;
+ type: "info" | "danger" | "warn" | "success";
+ timeout: number | undefined;
+}
+
+interface NotificationItem {
+ element: NotificationElement;
+ shownAt: Date;
+}
+
+@Component({
+ selector: "app-notification-bar",
+ templateUrl: "./notification-bar.component.html",
+ styleUrls: ["./notification-bar.component.scss"],
+ standalone: false
+})
+export class NotificationBarComponent {
+ items: NotificationItem[] = [];
+ isRunning: boolean;
+
+ constructor(private notificationService: NotificationService) {
+ this.isRunning = false;
+
+ this.notificationService.notification$.subscribe((notification) => {
+ if (notification !== undefined) {
+ this.items.push({
+ element: notification,
+ shownAt: new Date(),
+ });
+
+ this.startTimeoutIfNecessary();
+ }
+ });
+ }
+
+ startTimeoutIfNecessary(): void {
+ if (this.isRunning === true) {
+ return;
+ }
+
+ let intervalId = setInterval(() => {
+ this.isRunning = true;
+ let now = new Date();
+ this.items.forEach((item) => {
+ if (item.element.timeout !== undefined) {
+ let timeout = new Date(
+ item.shownAt.getTime() + item.element.timeout * 1000
+ );
+
+ if (timeout < now) {
+ this.dismiss(item);
+ }
+ }
+ });
+
+ if (this.items.length === 0) {
+ clearInterval(intervalId);
+ this.isRunning = false;
+ }
+ }, 1000);
+ }
+
+ dismiss(item: NotificationItem): void {
+ this.items = this.items.filter((i) => i !== item);
+ }
+}
diff --git a/src/app/core/components/theme-picker/theme-picker.component.html b/src/app/core/components/theme-picker/theme-picker.component.html
new file mode 100644
index 0000000..6d56f1e
--- /dev/null
+++ b/src/app/core/components/theme-picker/theme-picker.component.html
@@ -0,0 +1,23 @@
+
+
diff --git a/src/app/core/components/theme-picker/theme-picker.component.scss b/src/app/core/components/theme-picker/theme-picker.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/components/theme-picker/theme-picker.component.ts b/src/app/core/components/theme-picker/theme-picker.component.ts
new file mode 100644
index 0000000..7585ccd
--- /dev/null
+++ b/src/app/core/components/theme-picker/theme-picker.component.ts
@@ -0,0 +1,46 @@
+import { Component, OnInit } from "@angular/core";
+import { initFlowbite } from "flowbite";
+import { AppService } from "../../services/app.service";
+import { TranslateService } from "@ngx-translate/core";
+
+interface Theme {
+ imageSrc: string;
+ label: string;
+}
+
+@Component({
+ selector: "app-theme-picker",
+ templateUrl: "./theme-picker.component.html",
+ styleUrls: ["./theme-picker.component.scss"],
+ standalone: false
+})
+export class ThemePickerComponent implements OnInit {
+ themes: Theme[] = [
+ {
+ label: "dark",
+ imageSrc: "assets/dark.svg"
+ },
+ {
+ label: "light",
+ imageSrc: "assets/light.svg"
+ }
+ ];
+ selectedTheme: Theme;
+
+ constructor(
+ private appService: AppService,
+ ) {
+ this.selectedTheme = appService.darkMode ? this.themes[0] : this.themes[1];
+ }
+
+ ngOnInit(): void {
+ initFlowbite();
+ }
+
+ selectTheme(theme: Theme): void {
+ if (this.selectedTheme != theme) {
+ this.appService.toggleDarkMode();
+ this.selectedTheme = theme;
+ }
+ }
+}
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 2d58380..3e68725 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -1,15 +1,44 @@
-import { NgModule } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { HomeComponent } from './components/home/home.component';
-import { NavigationComponent } from './components/navigation/navigation.component';
-
-
+import { NgModule } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { AuthGuard } from "./guards/auth.guard";
+import { AuthService } from "./services/auth.service";
+import { RequestService } from "./services/request.service";
+import { AppService } from "./services/app.service";
+import { SharedModule } from "../shared/shared.module";
+import { RouterModule } from "@angular/router";
+import { ReactiveFormsModule } from "@angular/forms";
+import { NotificationBarComponent } from "./components/notification-bar/notification-bar.component";
+import { NotificationService } from "./services/notification.service";
+import { TranslateModule } from "@ngx-translate/core";
+import { AngularSvgIconModule } from 'angular-svg-icon';
+import { LanguagePickerComponent } from "./components/language-picker/language-picker.component";
+import { ThemePickerComponent } from "./components/theme-picker/theme-picker.component";
@NgModule({
- declarations: [HomeComponent, NavigationComponent],
- exports: [HomeComponent, NavigationComponent],
+ declarations: [
+ NotificationBarComponent,
+ LanguagePickerComponent,
+ ThemePickerComponent,
+ ],
+ exports: [
+ NotificationBarComponent,
+ LanguagePickerComponent,
+ ThemePickerComponent,
+ ],
imports: [
- CommonModule
- ]
+ AngularSvgIconModule,
+ CommonModule,
+ SharedModule,
+ RouterModule,
+ ReactiveFormsModule,
+ TranslateModule,
+ ],
+ providers: [
+ AuthGuard,
+ AuthService,
+ RequestService,
+ AppService,
+ NotificationService,
+ ],
})
-export class CoreModule { }
+export class CoreModule {}
diff --git a/src/app/core/guards/auth.guard.ts b/src/app/core/guards/auth.guard.ts
new file mode 100644
index 0000000..593591e
--- /dev/null
+++ b/src/app/core/guards/auth.guard.ts
@@ -0,0 +1,24 @@
+import { Injectable } from "@angular/core";
+import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from "@angular/router";
+import { filter, map, Observable } from "rxjs";
+import { AuthService } from "../services/auth.service";
+
+@Injectable()
+export class AuthGuard {
+ constructor(private authService: AuthService, private router: Router) {}
+
+ canActivate(): Observable | boolean {
+ return this.authService.currentState$
+ .pipe(
+ map(
+ (currentState) => {
+ if (currentState === null) {
+ this.router.navigateByUrl("/auth");
+ return false;
+ }
+ return true;
+ }
+ )
+ );
+ }
+}
diff --git a/src/app/core/home/components/home/home.component.html b/src/app/core/home/components/home/home.component.html
new file mode 100644
index 0000000..2b68548
--- /dev/null
+++ b/src/app/core/home/components/home/home.component.html
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/app/core/home/components/home/home.component.scss b/src/app/core/home/components/home/home.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/home/components/home/home.component.ts b/src/app/core/home/components/home/home.component.ts
new file mode 100644
index 0000000..13eb562
--- /dev/null
+++ b/src/app/core/home/components/home/home.component.ts
@@ -0,0 +1,9 @@
+import { Component } from "@angular/core";
+
+@Component({
+ selector: "app-home",
+ templateUrl: "./home.component.html",
+ styleUrls: ["./home.component.scss"],
+ standalone: false
+})
+export class HomeComponent {}
diff --git a/src/app/core/home/components/navigation/navigation.component.html b/src/app/core/home/components/navigation/navigation.component.html
new file mode 100644
index 0000000..4ae9b51
--- /dev/null
+++ b/src/app/core/home/components/navigation/navigation.component.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+ {{ "title" | translate }}
+
+
+
+
+
+
+
+
+
+
+
+ {{state.username}}
+ {{state.roleIdentifier}}
+
+
+ {{"menu.guest" | translate}}
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/core/home/components/navigation/navigation.component.scss b/src/app/core/home/components/navigation/navigation.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/home/components/navigation/navigation.component.ts b/src/app/core/home/components/navigation/navigation.component.ts
new file mode 100644
index 0000000..d41f4d4
--- /dev/null
+++ b/src/app/core/home/components/navigation/navigation.component.ts
@@ -0,0 +1,121 @@
+import { Component, OnInit } from "@angular/core";
+import { initFlowbite } from "flowbite";
+import { Router } from "@angular/router";
+import { StateResponse } from "src/app/core/models/user-state-request.model";
+import { AuthService } from "src/app/core/services/auth.service";
+
+interface NavigationLink {
+ imageSrc?: string;
+ label: string;
+ routerLink: string;
+ isVisible?: Function | undefined;
+}
+
+interface UserMenuButton {
+ label: Function | string;
+ routerLink?: string | undefined;
+ clickCallback?: Function | undefined;
+ isVisible?: Function | undefined;
+}
+
+@Component({
+ selector: "app-navigation",
+ templateUrl: "./navigation.component.html",
+ styleUrls: ["./navigation.component.scss"],
+ standalone: false
+})
+export class NavigationComponent implements OnInit {
+ navigationLinks: NavigationLink[] = [
+ {
+ routerLink: "/dashboard",
+ imageSrc: "assets/template.svg",
+ label: "navigation.home",
+ },
+ {
+ routerLink: "/location",
+ imageSrc: "assets/lightbulb.svg",
+ label: "navigation.locations",
+ isVisible: () => this.state?.roleIdentifier === 'admin'
+ },
+ {
+ routerLink: "/plant",
+ imageSrc: "assets/template.svg",
+ label: "navigation.plants",
+ isVisible: () => this.state?.roleIdentifier === 'admin'
+ },
+ {
+ routerLink: "/device",
+ imageSrc: "assets/plug.svg",
+ label: "navigation.devices",
+ isVisible: () => this.state?.roleIdentifier === 'admin'
+ },
+ {
+ routerLink: "/admin",
+ imageSrc: "assets/admin.svg",
+ label: "navigation.admin",
+ isVisible: () => this.state?.roleIdentifier === 'admin'
+ },
+ ];
+
+ usermenuButtons: UserMenuButton[] = [
+ {
+ label: () => "menu.settings",
+ routerLink: "/settings",
+ },
+ {
+ label: () => "menu.login",
+ clickCallback: () => {
+ this.router.navigateByUrl('/auth');
+ },
+ isVisible: () => this.state === undefined
+ },
+ {
+ label: () => "menu.logout",
+ clickCallback: () => {
+ this.logout();
+ },
+ isVisible: () => this.state !== undefined
+ },
+ ];
+
+ state: StateResponse | undefined;
+
+ constructor(
+ private authService: AuthService,
+ private router: Router,
+ ) {
+ this.state = undefined;
+
+ this.authService.readUserState().subscribe(response => {
+ this.authService.currentState$.next(response);
+ this.state = response;
+ });
+ }
+
+ ngOnInit(): void {
+ initFlowbite();
+ }
+
+ logout(): void {
+ this.authService
+ .logout()
+ .subscribe( response => {
+ this.state = undefined;
+ this.router.navigateByUrl('/dashboard')
+ });
+ }
+
+ clickUserMenuButtonLabel(button: UserMenuButton) {
+ if (button.clickCallback !== undefined) {
+ button.clickCallback();
+ }
+ }
+
+ getUserMenuButtonLabel(button: UserMenuButton) {
+ if (typeof button.label === "function") {
+ return button.label();
+ } else {
+ return button.label;
+ }
+ }
+}
diff --git a/src/app/core/home/components/settings/settings.component.html b/src/app/core/home/components/settings/settings.component.html
new file mode 100644
index 0000000..9f020c3
--- /dev/null
+++ b/src/app/core/home/components/settings/settings.component.html
@@ -0,0 +1,22 @@
+
+
+
+ {{ state.username }}
+
+
+ {{ state.roleIdentifier }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/core/home/components/settings/settings.component.scss b/src/app/core/home/components/settings/settings.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/home/components/settings/settings.component.ts b/src/app/core/home/components/settings/settings.component.ts
new file mode 100644
index 0000000..50028d7
--- /dev/null
+++ b/src/app/core/home/components/settings/settings.component.ts
@@ -0,0 +1,22 @@
+import { Component, OnInit } from "@angular/core";
+import { initFlowbite } from "flowbite";
+import { StateResponse } from "src/app/core/models/user-state-request.model";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-settings",
+ templateUrl: "./settings.component.html",
+ styleUrls: ["./settings.component.scss"],
+ standalone: false
+})
+export class SettingsComponent implements OnInit {
+ state?: StateResponse;
+
+ constructor(private authService: AuthService) {
+ this.authService.currentState$.subscribe(state => {this.state = state});
+ }
+
+ ngOnInit(): void {
+ initFlowbite();
+ }
+}
diff --git a/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.html b/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.html
new file mode 100644
index 0000000..07c4e45
--- /dev/null
+++ b/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.html
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.scss b/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.ts b/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.ts
new file mode 100644
index 0000000..fd4c60d
--- /dev/null
+++ b/src/app/core/home/components/settings/tabs/tab-profile/tab-profile.component.ts
@@ -0,0 +1,18 @@
+import { Component } from "@angular/core";
+
+interface Product {
+ description: string;
+ id: string;
+ price: number;
+ stock: number;
+}
+
+@Component({
+ selector: "app-tab-profile",
+ templateUrl: "./tab-profile.component.html",
+ styleUrls: ["./tab-profile.component.scss"],
+ standalone: false
+})
+export class TabProfileComponent {
+ constructor() {}
+}
diff --git a/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.html b/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.html
new file mode 100644
index 0000000..4ae615c
--- /dev/null
+++ b/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.html
@@ -0,0 +1,33 @@
+
diff --git a/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.scss b/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.ts b/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.ts
new file mode 100644
index 0000000..d5f3277
--- /dev/null
+++ b/src/app/core/home/components/settings/tabs/tab-security/tab-security.component.ts
@@ -0,0 +1,41 @@
+import { Component } from "@angular/core";
+import { FormControl, FormGroup, Validators } from "@angular/forms";
+import { AuthService } from "src/app/core/services/auth.service";
+
+@Component({
+ selector: "app-tab-security",
+ templateUrl: "./tab-security.component.html",
+ styleUrls: ["./tab-security.component.scss"],
+ standalone: false
+})
+export class TabSecurityComponent {
+ changePasswordForm = new FormGroup({
+ password: new FormControl("", [Validators.required]),
+ newPassword: new FormControl("", [Validators.required]),
+ newPasswordConfirmation: new FormControl("", [Validators.required]),
+ });
+
+ changeUsernameForm = new FormGroup({
+ password: new FormControl("", [Validators.required]),
+ newUsername: new FormControl("", [Validators.required]),
+ });
+
+ constructor(private authService: AuthService) {}
+
+ changePassword(): void {
+ if (this.changePasswordForm.value.newPassword !== this.changePasswordForm.value.newPasswordConfirmation)
+ return;
+
+ this.authService.changePassword({
+ password: this.changePasswordForm.value.password!,
+ newPassword: this.changePasswordForm.value.newPassword!,
+ }).subscribe(response => {});
+ }
+
+ changeUsername(): void {
+ this.authService.changeUsername({
+ password: this.changeUsernameForm.value.password!,
+ newUsername: this.changeUsernameForm.value.newUsername!,
+ }).subscribe(response => {});
+ }
+}
diff --git a/src/app/core/home/home.module.ts b/src/app/core/home/home.module.ts
new file mode 100644
index 0000000..2e2d895
--- /dev/null
+++ b/src/app/core/home/home.module.ts
@@ -0,0 +1,82 @@
+import { NgModule } from "@angular/core";
+import { CommonModule } from "@angular/common";
+import { RouterModule, Routes } from "@angular/router";
+import { ReactiveFormsModule } from "@angular/forms";
+import { TranslateModule } from "@ngx-translate/core";
+import { SharedModule } from "src/app/shared/shared.module";
+import { AngularSvgIconModule } from "angular-svg-icon";
+import { HomeComponent } from "./components/home/home.component";
+import { AuthGuard } from "../guards/auth.guard";
+import { SettingsComponent } from "./components/settings/settings.component";
+import { TabProfileComponent } from "./components/settings/tabs/tab-profile/tab-profile.component";
+import { TabSecurityComponent } from "./components/settings/tabs/tab-security/tab-security.component";
+import { NavigationComponent } from "./components/navigation/navigation.component";
+import { CoreModule } from "../core.module";
+
+const routes: Routes = [
+ {
+ path: "",
+ component: HomeComponent,
+ children: [
+ {
+ path: "settings",
+ canActivate: [AuthGuard],
+ component: SettingsComponent
+ },
+ {
+ path: "dashboard",
+ loadChildren: () =>
+ import('../../feature/dashboard/dashboard.module').then((m) => m.DashboardModule)
+ },
+ {
+ path: "plant",
+ canActivate: [AuthGuard],
+ loadChildren: () =>
+ import('../../feature/plant/plant.module').then((m) => m.PlantModule)
+ },
+ {
+ path: "device",
+ canActivate: [AuthGuard],
+ loadChildren: () =>
+ import('../../feature/device/device.module').then((m) => m.DeviceModule)
+ },
+ {
+ path: "location",
+ canActivate: [AuthGuard],
+ loadChildren: () =>
+ import('../../feature/location/location.module').then((m) => m.LocationModule)
+ },
+ {
+ path: "admin",
+ canActivate: [AuthGuard],
+ loadChildren: () =>
+ import('../../feature/admin/admin.module').then((m) => m.AdminModule)
+ },
+ {
+ path: '**',
+ redirectTo: 'dashboard',
+ pathMatch: 'full'
+ }
+ ],
+ },
+];
+
+@NgModule({
+ declarations: [
+ SettingsComponent,
+ NavigationComponent,
+ TabProfileComponent,
+ TabSecurityComponent,
+ HomeComponent
+ ],
+ imports: [
+ RouterModule.forChild(routes),
+ AngularSvgIconModule,
+ CommonModule,
+ ReactiveFormsModule,
+ TranslateModule,
+ SharedModule,
+ CoreModule
+ ]
+})
+export class HomeModule {}
diff --git a/src/app/core/models/change-password-request.model.ts b/src/app/core/models/change-password-request.model.ts
new file mode 100644
index 0000000..d612faf
--- /dev/null
+++ b/src/app/core/models/change-password-request.model.ts
@@ -0,0 +1,7 @@
+export interface ChangePasswordRequest {
+ password: string;
+ newPassword: string;
+}
+
+export interface ChangePasswordResponse {
+}
diff --git a/src/app/core/models/change-username-request.model.ts b/src/app/core/models/change-username-request.model.ts
new file mode 100644
index 0000000..1137d5b
--- /dev/null
+++ b/src/app/core/models/change-username-request.model.ts
@@ -0,0 +1,7 @@
+export interface ChangeUsernameRequest {
+ password: string;
+ newUsername: string;
+}
+
+export interface ChangeUsernameResponse {
+}
diff --git a/src/app/core/models/confirm-registration-request.model.ts b/src/app/core/models/confirm-registration-request.model.ts
new file mode 100644
index 0000000..cd5a493
--- /dev/null
+++ b/src/app/core/models/confirm-registration-request.model.ts
@@ -0,0 +1,12 @@
+export interface ConfirmRegistrationRequest {
+ id: string;
+ password: string;
+ passwordConfirmation: string;
+}
+
+export interface ConfirmRegistrationResponse {
+ id: string;
+ username: string;
+ roleIdentifier: "admin"|"user";
+ permissions: string[];
+}
diff --git a/src/app/core/models/forgot-password-request.model.ts b/src/app/core/models/forgot-password-request.model.ts
new file mode 100644
index 0000000..95d83c3
--- /dev/null
+++ b/src/app/core/models/forgot-password-request.model.ts
@@ -0,0 +1,6 @@
+export interface ForgotPasswordRequest {
+ mail: string;
+}
+
+export interface ForgotPasswordResponse {
+}
diff --git a/src/app/core/models/login-request.model.ts b/src/app/core/models/login-request.model.ts
new file mode 100644
index 0000000..7d4d58e
--- /dev/null
+++ b/src/app/core/models/login-request.model.ts
@@ -0,0 +1,8 @@
+export interface LoginUserRequest {
+ identifier: string;
+ password: string;
+}
+
+export interface LoginUserResponse {
+ sessionId: string;
+}
diff --git a/src/app/core/models/logout-request.model.ts b/src/app/core/models/logout-request.model.ts
new file mode 100644
index 0000000..0bbb074
--- /dev/null
+++ b/src/app/core/models/logout-request.model.ts
@@ -0,0 +1,6 @@
+export interface LogoutUserRequest {
+}
+
+export interface LogoutUserResponse {
+ response?: string|undefined;
+}
diff --git a/src/app/core/models/register-user-request.model.ts b/src/app/core/models/register-user-request.model.ts
new file mode 100644
index 0000000..4a1df17
--- /dev/null
+++ b/src/app/core/models/register-user-request.model.ts
@@ -0,0 +1,8 @@
+export interface RegisterUserRequest {
+ username: string;
+ mail: string;
+}
+
+export interface RegisterUserResponse {
+ response?: string|undefined;
+}
diff --git a/src/app/core/models/reset-password-request.model.ts b/src/app/core/models/reset-password-request.model.ts
new file mode 100644
index 0000000..d45ebf6
--- /dev/null
+++ b/src/app/core/models/reset-password-request.model.ts
@@ -0,0 +1,8 @@
+export interface ResetPasswordRequest {
+ passwordToken: string;
+ newPassword: string;
+ passwordConfirmation: string;
+}
+
+export interface ResetPasswordResponse {
+}
diff --git a/src/app/core/models/user-state-request.model.ts b/src/app/core/models/user-state-request.model.ts
new file mode 100644
index 0000000..634a869
--- /dev/null
+++ b/src/app/core/models/user-state-request.model.ts
@@ -0,0 +1,13 @@
+export interface StateRequest {
+}
+
+export interface StateResponse {
+ id: string;
+ username: string;
+ roleIdentifier: any|null;
+ permissions: string[];
+ createdAt: string;
+ updatedAt: string;
+ sessionId: string;
+ role?: "admin"|"user"|undefined;
+}
diff --git a/src/app/core/services/app.service.ts b/src/app/core/services/app.service.ts
new file mode 100644
index 0000000..8026d53
--- /dev/null
+++ b/src/app/core/services/app.service.ts
@@ -0,0 +1,30 @@
+import { Injectable } from "@angular/core";
+
+@Injectable()
+export class AppService {
+ darkMode: boolean;
+
+ constructor() {
+ this.darkMode =
+ window.matchMedia &&
+ window.matchMedia("(prefers-color-scheme: dark)").matches;
+ this.applyDarkMode();
+ }
+
+ private applyDarkMode(): void {
+ if (this.darkMode) {
+ document.documentElement.classList.add("theme-dark");
+ } else {
+ document.documentElement.classList.remove("theme-dark");
+ }
+ }
+
+ toggleDarkMode() {
+ this.darkMode = !this.darkMode;
+ this.applyDarkMode();
+ }
+
+ getDarkMode(): boolean {
+ return this.darkMode;
+ }
+}
diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts
new file mode 100644
index 0000000..a4a296d
--- /dev/null
+++ b/src/app/core/services/auth.service.ts
@@ -0,0 +1,122 @@
+import { Injectable } from "@angular/core";
+import { RequestService } from "./request.service";
+import { StateResponse } from "../models/user-state-request.model";
+import { BehaviorSubject, catchError, Observable } from "rxjs";
+import { LoginUserRequest, LoginUserResponse } from "../models/login-request.model";
+import { Router } from "@angular/router";
+import {
+ RegisterUserRequest,
+ RegisterUserResponse,
+} from "../models/register-user-request.model";
+import {
+ ConfirmRegistrationRequest,
+ ConfirmRegistrationResponse,
+} from "../models/confirm-registration-request.model";
+import {
+ ResetPasswordRequest,
+ ResetPasswordResponse,
+} from "../models/reset-password-request.model";
+import {
+ ForgotPasswordRequest,
+ ForgotPasswordResponse,
+} from "../models/forgot-password-request.model";
+import {
+ ChangePasswordRequest,
+ ChangePasswordResponse,
+} from "../models/change-password-request.model";
+import {
+ ChangeUsernameRequest,
+ ChangeUsernameResponse,
+} from "../models/change-username-request.model";
+import { LogoutUserResponse } from "../models/logout-request.model";
+
+@Injectable()
+export class AuthService {
+ currentState$ = new BehaviorSubject(
+ undefined
+ );
+
+ constructor(private requestService: RequestService, private router: Router) {}
+
+ readUserState()
+ {
+ return this.requestService.request(
+ 'get',
+ this.requestService.obtainUrl("user/state"),
+ {}
+ )
+ .pipe(
+ catchError(
+ error => {
+ this.currentState$.next(undefined);
+ throw 'User State not readable';
+ }
+ )
+ );
+ }
+
+ logout(): Observable {
+ this.currentState$.next(undefined);
+ return this.requestService.call(
+ "get",
+ "auth/logout-user",
+ {}
+ );
+ }
+
+ login(body: LoginUserRequest): Observable {
+ return this.requestService.call(
+ 'post',
+ "auth/login-user",
+ body
+ );
+ }
+
+ register(body: RegisterUserRequest): Observable {
+ return this.requestService.call(
+ "post",
+ "auth/register-user",
+ body,
+ );
+ }
+
+ confirmRegistration(body: ConfirmRegistrationRequest): Observable {
+ return this.requestService.call(
+ 'post',
+ 'auth/confirm-registration',
+ body,
+ );
+ }
+
+ resetPassword(body: ResetPasswordRequest): Observable {
+ return this.requestService.call(
+ "post",
+ "auth/reset-password",
+ body,
+ );
+ }
+
+ forgotPassword(body: ForgotPasswordRequest): Observable {
+ return this.requestService.call(
+ 'post',
+ "auth/forgot-password",
+ body,
+ );
+ }
+
+ changePassword(body: ChangePasswordRequest): Observable {
+ return this.requestService.call(
+ 'post',
+ "user/change-password",
+ body,
+ );
+ }
+
+ changeUsername(body: ChangeUsernameRequest): Observable {
+ return this.requestService.call(
+ 'post',
+ "user/change-username",
+ body,
+ );
+ }
+}
diff --git a/src/app/core/services/notification.service.ts b/src/app/core/services/notification.service.ts
new file mode 100644
index 0000000..a53ca9c
--- /dev/null
+++ b/src/app/core/services/notification.service.ts
@@ -0,0 +1,32 @@
+import { Injectable } from "@angular/core";
+import { BehaviorSubject, Observable, Subject } from "rxjs";
+import { NotificationElement } from "../components/notification-bar/notification-bar.component";
+
+@Injectable()
+export class NotificationService {
+ notification$ = new Subject();
+
+ push(message: string, type: "info" | "danger" | "warn" | "success"): void {
+ let timeout = undefined;
+ switch (type) {
+ case "info":
+ timeout = 5;
+ break;
+ case "danger":
+ timeout = 10;
+ break;
+ case "warn":
+ timeout = 7;
+ break;
+ case "success":
+ timeout = 3;
+ break;
+ }
+
+ this.notification$.next({
+ message: message,
+ type: type,
+ timeout: timeout,
+ });
+ }
+}
diff --git a/src/app/core/services/request.service.ts b/src/app/core/services/request.service.ts
index 38ec3fa..9ed625d 100644
--- a/src/app/core/services/request.service.ts
+++ b/src/app/core/services/request.service.ts
@@ -1,105 +1,83 @@
-import { HttpClient } from '@angular/common/http';
-import { Router } from '@angular/router';
-import { Injectable } from '@angular/core';
-import { MatSnackBar } from '@angular/material/snack-bar';
+import { HttpClient } from "@angular/common/http";
+import { Router } from "@angular/router";
+import { Injectable } from "@angular/core";
+import { NotificationService } from "./notification.service";
+import { TranslateService } from "@ngx-translate/core";
+import { Observable, catchError, first, map, take } from "rxjs";
@Injectable({
- providedIn: 'root'
+ providedIn: "root",
})
export class RequestService {
-
constructor(
- private http: HttpClient,
+ private http: HttpClient,
private router: Router,
- private snackBar: MatSnackBar
- ) {
- }
+ private notificationService: NotificationService,
+ private translate: TranslateService
+ ) {}
- post(apiPath: string, body: any, fct: Function)
- {
- let url = this.obtainUrl(apiPath);
- let observable = this.http.post(url, body).subscribe(
- (answer:any) => {
- fct(answer);
- },
- (error:any) => {
- this.handleError(error);
- }
+ public call(method: 'post' | 'get', path: string, body: any|undefined): Observable {
+ return this.request(
+ method,
+ this.obtainUrl(path),
+ body
+ ).pipe(
+ catchError(
+ error => {
+ this.handleError(error)
+ throw 'Error occurred, during API call'
+ }
+ )
);
}
- postFiles(apiPath: string, files: File[], fct: Function) {
- if (!files || files.length === 0) {
- throw 'Need to select at least one file';
+ public request(method:'get'|'post', url: string, body: any|undefined): Observable {
+ if(method === 'get') {
+ return this.http.get(url);
+ } else {
+ return this.http.post(url, body);
}
-
- const formData = new FormData();
-
- for (let fileIndex = 0; fileIndex < files.length; fileIndex++) {
- formData.append('file' + fileIndex, files[fileIndex]);
- }
-
- let url = this.obtainUrl(apiPath);
- let observable = this.http.post(url, formData).subscribe(
- (answer: any) => {
- fct(answer);
- },
- (error: any) => {
- this.handleError(error);
- }
- );
- }
-
- get(apiPath: string, body: any, fct: Function)
- {
- let url = this.obtainUrl(apiPath);
- let observable = this.http.get(url, body).subscribe(
- (answer:any) => {
- fct(answer);
- },
- (error:any) => {
- this.handleError(error);
- }
- );
}
- private obtainUrl(apiPath: string): string {
+ public obtainUrl(apiPath: string): string {
let hostString = window.location.host;
let protocol = window.location.protocol;
- return protocol + '//' + hostString + '/api/' + apiPath;
+ return protocol + "//" + hostString + "/api/" + apiPath;
}
- private handleError(answer: any): void {
-
+ public handleError(answer: any): void {
if (answer.status == 401) {
- console.log('Deine Sitzung konnte nicht gefunden werden');
- this.router.navigate(['/auth']);
+ this.notificationService.push(
+ this.translate.instant("error.unauthorized"),
+ 'info'
+ );
+ this.router.navigate(["/auth"]);
return;
}
-
+
if (answer.status == 403) {
- this.showSnackBar('Du bist nicht für diese Aktion autorisiert', 'Ok');
+ this.notificationService.push(
+ this.translate.instant("error.forbidden"),
+ 'info'
+ );
return;
}
-
+
try {
let errorObject = answer.error.error;
- if (errorObject.hasOwnProperty('message')) {
+ if (errorObject.hasOwnProperty("code")) {
+ throw (
+ this.translate.instant("error." + errorObject.code.toString()) ??
+ errorObject.code.toString()
+ );
+ }
+
+ if (errorObject.hasOwnProperty("message")) {
throw errorObject.message.toString();
}
-
- if (errorObject.hasOwnProperty('code')) {
- throw errorObject.code.toString();
- }
- } catch(error:any) {
- this.showSnackBar(error.toString(), 'Ok');
+ } catch (error: any) {
+ this.notificationService.push(error.toString(), 'danger');
}
}
-
- private showSnackBar(message: string, action?: string) {
- this.snackBar.open(message.toString(), action, {
- duration: 3000,
- });
- }
}
diff --git a/src/app/shared/components/button/button.component.html b/src/app/shared/components/button/button.component.html
new file mode 100644
index 0000000..3e5dc07
--- /dev/null
+++ b/src/app/shared/components/button/button.component.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/button/button.component.scss b/src/app/shared/components/button/button.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/components/button/button.component.ts b/src/app/shared/components/button/button.component.ts
new file mode 100644
index 0000000..a5b7a53
--- /dev/null
+++ b/src/app/shared/components/button/button.component.ts
@@ -0,0 +1,13 @@
+import { Component, EventEmitter, inject, Input, Output } from "@angular/core";
+import { ControlContainer, FormGroup } from "@angular/forms";
+
+@Component({
+ selector: "shared-button",
+ templateUrl: "./button.component.html",
+ styleUrl: "./button.component.scss",
+ standalone: false
+})
+export class ButtonComponent {
+ @Output() onClick = new EventEmitter();
+ @Input({ required: true }) label!: string;
+}
diff --git a/src/app/shared/components/card/card.component.html b/src/app/shared/components/card/card.component.html
index 02ae685..1925a7f 100644
--- a/src/app/shared/components/card/card.component.html
+++ b/src/app/shared/components/card/card.component.html
@@ -1,31 +1,11 @@
-
-