diff --git a/public/icons/filter.svg b/public/icons/filter.svg
new file mode 100644
index 0000000..c3708ec
--- /dev/null
+++ b/public/icons/filter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/menu.svg b/public/icons/menu.svg
new file mode 100644
index 0000000..a104aea
--- /dev/null
+++ b/public/icons/menu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/icons/plus.svg b/public/icons/plus.svg
new file mode 100644
index 0000000..7a1e404
--- /dev/null
+++ b/public/icons/plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/core/components/cookie-consent/cookie-consent.component.ts b/src/app/core/components/cookie-consent/cookie-consent.component.ts
index f6ffca7..14e7a3b 100644
--- a/src/app/core/components/cookie-consent/cookie-consent.component.ts
+++ b/src/app/core/components/cookie-consent/cookie-consent.component.ts
@@ -1,53 +1,53 @@
-import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
-import { isPlatformBrowser, CommonModule } from '@angular/common'; // isPlatformBrowser importieren
+// /src/app/core/components/cookie-consent/cookie-consent.component.ts
+
+import { Component, OnInit, inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { StorageService } from '../../services/storage.service'; // <-- NEUER IMPORT
+
+// Ein Enum für die verschiedenen Zustände ist typsicherer als reine Strings
+type ConsentStatus = 'accepted' | 'declined';
@Component({
selector: 'app-cookie-consent',
+ standalone: true, // <-- standalone: true hinzufügen, falls es fehlt
imports: [CommonModule],
templateUrl: './cookie-consent.component.html',
styleUrl: './cookie-consent.component.css'
})
export class CookieConsentComponent implements OnInit {
+ // --- Abhängigkeiten mit moderner inject()-Syntax ---
+ private storageService = inject(StorageService);
isVisible = false;
private readonly consentKey = 'cookie_consent_status';
- private isBrowser: boolean;
- // Wir injizieren PLATFORM_ID, um die aktuelle Plattform (Browser/Server) zu ermitteln.
- constructor(@Inject(PLATFORM_ID) private platformId: Object) {
- // Speichere das Ergebnis in einer Eigenschaft, um es wiederverwenden zu können.
- this.isBrowser = isPlatformBrowser(this.platformId);
- }
+ // Der Konstruktor wird viel sauberer oder kann ganz entfallen
+ constructor() {}
ngOnInit(): void {
- // Wir führen die Prüfung nur aus, wenn der Code im Browser läuft.
- if (this.isBrowser) {
- this.checkConsent();
- }
+ // Der Service kümmert sich um die Browser-Prüfung.
+ // Wir können ihn einfach immer aufrufen.
+ this.checkConsent();
}
private checkConsent(): void {
- const consent = localStorage.getItem(this.consentKey);
+ const consent = this.storageService.getItem(this.consentKey);
+ // Das Banner wird nur angezeigt, wenn noch gar nichts gespeichert wurde (consent ist null)
if (!consent) {
this.isVisible = true;
}
}
accept(): void {
- // Wir stellen sicher, dass wir localStorage nur im Browser schreiben.
- if (this.isBrowser) {
- localStorage.setItem(this.consentKey, 'accepted');
- this.isVisible = false;
- console.log('Cookies wurden akzeptiert.');
- }
+ // Der Service kümmert sich um die Browser-Prüfung und Serialisierung.
+ this.storageService.setItem(this.consentKey, 'accepted');
+ this.isVisible = false;
+ console.log('Cookies wurden akzeptiert.');
}
decline(): void {
- // Wir stellen sicher, dass wir localStorage nur im Browser schreiben.
- if (this.isBrowser) {
- localStorage.setItem(this.consentKey, 'declined');
- this.isVisible = false;
- console.log('Cookies wurden abgelehnt.');
- }
+ this.storageService.setItem(this.consentKey, 'declined');
+ this.isVisible = false;
+ console.log('Cookies wurden abgelehnt.');
}
}
\ No newline at end of file
diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts
index 17ce50e..d5faf32 100644
--- a/src/app/core/services/auth.service.ts
+++ b/src/app/core/services/auth.service.ts
@@ -1,17 +1,18 @@
// /src/app/core/services/auth.service.ts
-import { Injectable, PLATFORM_ID, inject } from '@angular/core';
+import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { isPlatformBrowser } from '@angular/common';
import { Router } from '@angular/router';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { jwtDecode } from 'jwt-decode';
+// Eigene Imports
import { LoginRequest, AuthResponse, RegisterRequest } from '../models/auth.models';
import { API_URL } from '../tokens/api-url.token';
+import { StorageService } from './storage.service';
-// Ein Hilfs-Interface, um die Struktur des dekodierten Tokens typsicher zu machen.
+// Hilfs-Interface für die dekodierten Token-Daten.
interface DecodedToken {
exp: number; // Expiration time als UNIX-Timestamp in Sekunden
// Hier könnten weitere Claims wie 'email', 'sub', 'role' etc. stehen
@@ -21,24 +22,22 @@ interface DecodedToken {
providedIn: 'root'
})
export class AuthService {
- // --- Injizierte Abhängigkeiten mit moderner Syntax ---
+ // --- Injizierte Abhängigkeiten ---
private http = inject(HttpClient);
private router = inject(Router);
- private platformId = inject(PLATFORM_ID);
+ private storageService = inject(StorageService); // <-- NEU
private apiUrl = inject(API_URL);
private readonly endpoint = '/Auth';
private readonly TOKEN_KEY = 'auth-token';
private readonly ROLES_KEY = 'auth-user-roles';
- private loggedInStatus = new BehaviorSubject(this.isBrowser() && !!this.getToken());
+ private loggedInStatus = new BehaviorSubject(!!this.getToken());
public isLoggedIn$ = this.loggedInStatus.asObservable();
private tokenExpirationTimer: any;
constructor() {
- // Beim Initialisieren des Services prüfen, ob bereits ein Token vorhanden ist
- // und ggf. den Logout-Timer dafür starten (z.B. nach einem Neuladen der Seite).
this.initTokenCheck();
}
@@ -47,7 +46,7 @@ export class AuthService {
tap(response => {
if (response?.isAuthSuccessful) {
this.setSession(response);
- this.startTokenExpirationTimer(); // Timer nach erfolgreichem Login starten
+ this.startTokenExpirationTimer();
}
}),
catchError(() => {
@@ -62,7 +61,7 @@ export class AuthService {
tap(response => {
if (response?.isAuthSuccessful) {
this.setSession(response);
- this.startTokenExpirationTimer(); // Timer nach erfolgreichem Login starten
+ this.startTokenExpirationTimer();
}
}),
catchError(() => {
@@ -80,7 +79,6 @@ export class AuthService {
logout(): void {
this.clearSession();
- // Den proaktiven Timer stoppen, da der Logout manuell erfolgt.
if (this.tokenExpirationTimer) {
clearTimeout(this.tokenExpirationTimer);
}
@@ -88,13 +86,11 @@ export class AuthService {
}
getToken(): string | null {
- return this.isBrowser() ? localStorage.getItem(this.TOKEN_KEY) : null;
+ return this.storageService.getItem(this.TOKEN_KEY);
}
getUserRoles(): string[] {
- if (!this.isBrowser()) return [];
- const roles = localStorage.getItem(this.ROLES_KEY);
- return roles ? JSON.parse(roles) : [];
+ return this.storageService.getItem(this.ROLES_KEY) || [];
}
hasRole(requiredRole: string): boolean {
@@ -102,25 +98,19 @@ export class AuthService {
}
private setSession(authResponse: AuthResponse): void {
- if (this.isBrowser() && authResponse?.token && authResponse?.roles) {
- localStorage.setItem(this.TOKEN_KEY, authResponse.token);
- localStorage.setItem(this.ROLES_KEY, JSON.stringify(authResponse.roles));
+ if (authResponse?.token && authResponse?.roles) {
+ this.storageService.setItem(this.TOKEN_KEY, authResponse.token);
+ this.storageService.setItem(this.ROLES_KEY, authResponse.roles);
this.loggedInStatus.next(true);
}
}
private clearSession(): void {
- if (this.isBrowser()) {
- localStorage.removeItem(this.TOKEN_KEY);
- localStorage.removeItem(this.ROLES_KEY);
- }
+ this.storageService.removeItem(this.TOKEN_KEY);
+ this.storageService.removeItem(this.ROLES_KEY);
this.loggedInStatus.next(false);
}
- private isBrowser(): boolean {
- return isPlatformBrowser(this.platformId);
- }
-
private startTokenExpirationTimer(): void {
if (this.tokenExpirationTimer) {
clearTimeout(this.tokenExpirationTimer);
@@ -133,18 +123,21 @@ export class AuthService {
try {
const decodedToken = jwtDecode(token);
- const expirationDate = new Date(decodedToken.exp * 1000); // JWT 'exp' ist in Sekunden, Date() braucht Millisekunden
+ const expirationDate = new Date(decodedToken.exp * 1000);
const timeoutDuration = expirationDate.getTime() - new Date().getTime();
- if (timeoutDuration > 0) {
+ // Puffer, um Clock-Skew-Probleme zu vermeiden
+ const clockSkewBuffer = 5000; // 5 Sekunden
+
+ if (timeoutDuration > clockSkewBuffer) {
this.tokenExpirationTimer = setTimeout(() => {
console.warn('Sitzung proaktiv beendet, da das Token abgelaufen ist.');
this.logout();
- // Hier könnte man eine Snackbar-Nachricht anzeigen
}, timeoutDuration);
} else {
- // Das gespeicherte Token ist bereits abgelaufen
- this.clearSession();
+ if (this.getToken()) {
+ this.logout();
+ }
}
} catch (error) {
console.error('Fehler beim Dekodieren des Tokens. Session wird bereinigt.', error);
@@ -153,8 +146,7 @@ export class AuthService {
}
private initTokenCheck(): void {
- if (this.isBrowser()) {
- this.startTokenExpirationTimer();
- }
+ // Der StorageService ist bereits SSR-sicher, wir brauchen hier keine extra Prüfung.
+ this.startTokenExpirationTimer();
}
}
\ No newline at end of file
diff --git a/src/app/core/services/storage.service.ts b/src/app/core/services/storage.service.ts
new file mode 100644
index 0000000..1206a82
--- /dev/null
+++ b/src/app/core/services/storage.service.ts
@@ -0,0 +1,87 @@
+// /src/app/core/services/storage.service.ts
+
+import { Injectable, PLATFORM_ID, inject } from '@angular/core';
+import { isPlatformBrowser } from '@angular/common';
+
+// Definiert die verfügbaren Speichertypen
+export type StorageType = 'localStorage' | 'sessionStorage';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class StorageService {
+ private platformId = inject(PLATFORM_ID);
+
+ /**
+ * Speichert einen Wert im angegebenen Web Storage.
+ * Serialisiert Objekte automatisch zu JSON.
+ * @param key Der Schlüssel, unter dem der Wert gespeichert wird.
+ * @param value Der zu speichernde Wert.
+ * @param storageType Der zu verwendende Speicher ('localStorage' oder 'sessionStorage'). Standard ist 'localStorage'.
+ */
+ setItem(key: string, value: T, storageType: StorageType = 'localStorage'): void {
+ if (isPlatformBrowser(this.platformId)) {
+ try {
+ const storage = this.getStorage(storageType);
+ const serializedValue = JSON.stringify(value);
+ storage.setItem(key, serializedValue);
+ } catch (e) {
+ console.error(`Error saving to ${storageType} with key "${key}"`, e);
+ }
+ }
+ }
+
+ /**
+ * Ruft einen Wert aus dem angegebenen Web Storage ab.
+ * Deserialisiert JSON-Strings automatisch in Objekte.
+ * @param key Der Schlüssel des abzurufenden Wertes.
+ * @param storageType Der zu verwendende Speicher ('localStorage' oder 'sessionStorage'). Standard ist 'localStorage'.
+ * @returns Der abgerufene Wert (typsicher) oder null, wenn der Schlüssel nicht existiert oder ein Fehler auftritt.
+ */
+ getItem(key: string, storageType: StorageType = 'localStorage'): T | null {
+ if (isPlatformBrowser(this.platformId)) {
+ try {
+ const storage = this.getStorage(storageType);
+ const serializedValue = storage.getItem(key);
+ if (serializedValue === null) {
+ return null;
+ }
+ return JSON.parse(serializedValue) as T;
+ } catch (e) {
+ console.error(`Error reading from ${storageType} with key "${key}"`, e);
+ return null;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Entfernt einen Wert aus dem angegebenen Web Storage.
+ * @param key Der Schlüssel des zu entfernenden Wertes.
+ * @param storageType Der zu verwendende Speicher ('localStorage' oder 'sessionStorage'). Standard ist 'localStorage'.
+ */
+ removeItem(key: string, storageType: StorageType = 'localStorage'): void {
+ if (isPlatformBrowser(this.platformId)) {
+ const storage = this.getStorage(storageType);
+ storage.removeItem(key);
+ }
+ }
+
+ /**
+ * Löscht alle Einträge im angegebenen Web Storage.
+ * @param storageType Der zu leerende Speicher ('localStorage' oder 'sessionStorage'). Standard ist 'localStorage'.
+ */
+ clear(storageType: StorageType = 'localStorage'): void {
+ if (isPlatformBrowser(this.platformId)) {
+ const storage = this.getStorage(storageType);
+ storage.clear();
+ }
+ }
+
+ /**
+ * Private Hilfsfunktion, um das korrekte Storage-Objekt zurückzugeben.
+ */
+ private getStorage(storageType: StorageType): Storage {
+ return storageType === 'localStorage' ? window.localStorage : window.sessionStorage;
+ }
+}
\ No newline at end of file
diff --git a/src/app/features/components/dashboard/dashboard-page/dashboard-page.component.ts b/src/app/features/components/dashboard/dashboard-page/dashboard-page.component.ts
index cdfc937..4b681c5 100644
--- a/src/app/features/components/dashboard/dashboard-page/dashboard-page.component.ts
+++ b/src/app/features/components/dashboard/dashboard-page/dashboard-page.component.ts
@@ -66,6 +66,30 @@ export class DashboardPageComponent {
amount: '€87.00',
status: 'info', // NEU: Sprechender Status
},
+ {
+ id: 'a2d4b',
+ user: { name: 'Max Mustermann', email: 'max@test.de', avatarUrl: 'https://i.pravatar.cc/150?u=max' },
+ amount: '€129.99',
+ status: 'completed', // NEU: Sprechender Status
+ },
+ {
+ id: 'f8e9c',
+ user: { name: 'Erika Musterfrau', email: 'erika@test.de', avatarUrl: 'https://i.pravatar.cc/150?u=erika' },
+ amount: '€49.50',
+ status: 'processing', // NEU: Sprechender Status
+ },
+ {
+ id: 'h1g3j',
+ user: { name: 'John Doe', email: 'john.d@test.com', avatarUrl: 'https://i.pravatar.cc/150?u=john' },
+ amount: '€87.00',
+ status: 'cancelled', // NEU: Sprechender Status
+ },
+ {
+ id: 'h1g3j',
+ user: { name: 'John Doe', email: 'john.d@test.com', avatarUrl: 'https://i.pravatar.cc/150?u=john' },
+ amount: '€87.00',
+ status: 'info', // NEU: Sprechender Status
+ },
];
handleDeleteOrder(orderId: string): void {
diff --git a/src/app/shared/components/layout/sidebar/sidebar.component.ts b/src/app/shared/components/layout/sidebar/sidebar.component.ts
index e83d45d..29bae14 100644
--- a/src/app/shared/components/layout/sidebar/sidebar.component.ts
+++ b/src/app/shared/components/layout/sidebar/sidebar.component.ts
@@ -1,76 +1,51 @@
-import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
-import { CommonModule, isPlatformBrowser } from '@angular/common';
+// /src/app/core/components/default-layout/sidebar/sidebar.component.ts
+
+import { Component, OnInit, inject } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterLink, RouterLinkActive } from '@angular/router'; // RouterLink und RouterLinkActive importieren
import { IconComponent } from '../../ui/icon/icon.component';
+import { StorageService } from '../../../../core/services/storage.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-sidebar',
standalone: true,
- imports: [CommonModule, IconComponent],
+ imports: [CommonModule, IconComponent], // RouterLink und RouterLinkActive hier hinzufügen
templateUrl: './sidebar.component.html',
styleUrl: './sidebar.component.css',
})
export class SidebarComponent implements OnInit {
- // 1. OnInit implementieren
- // Key für localStorage, genau wie beim Dark Mode
- private readonly sidebarCollapsedKey = 'app-sidebar-collapsed-setting';
+ // --- Abhängigkeiten mit moderner inject()-Syntax ---
+ private storageService = inject(StorageService);
- // Dummy-Eigenschaft für die aktive Route
- activeRoute = 'dashboard';
-
- // Der Standardwert ist 'false' (aufgeklappt), wird aber sofort überschrieben
+ private readonly sidebarCollapsedKey = 'app-sidebar-collapsed';
public isCollapsed = false;
- // 2. PLATFORM_ID injizieren, um localStorage sicher zu verwenden
- constructor(
- @Inject(PLATFORM_ID) private platformId: Object,
- private router: Router
- ) {}
+ activeRoute = 'dashboard';
+ constructor(private router: Router) {}
- // 3. Beim Start der Komponente den gespeicherten Zustand laden
ngOnInit(): void {
this.loadCollapsedState();
}
- // Dummy-Methode
setActive(route: string): void {
this.activeRoute = route;
this.router.navigateByUrl('/shop/' + route);
-
}
- // 4. Die Umschalt-Methode aktualisieren, damit sie den neuen Zustand speichert
toggleSidebar(): void {
- // Zuerst den Zustand ändern
this.isCollapsed = !this.isCollapsed;
- // Dann den neuen Zustand speichern
this.saveCollapsedState();
}
- // 5. Methode zum Laden des Zustands (kopiert vom Dark-Mode-Muster)
private loadCollapsedState(): void {
- if (isPlatformBrowser(this.platformId)) {
- try {
- const storedValue = localStorage.getItem(this.sidebarCollapsedKey);
- // Setze den Zustand der Komponente basierend auf dem gespeicherten Wert
- this.isCollapsed = storedValue === 'true';
- } catch (e) {
- console.error('Could not access localStorage for sidebar state:', e);
- }
- }
+ // Der Service kümmert sich um die Browser-Prüfung und gibt boolean oder null zurück
+ this.isCollapsed =
+ this.storageService.getItem(this.sidebarCollapsedKey) ?? false;
}
- // 6. Methode zum Speichern des Zustands (kopiert vom Dark-Mode-Muster)
private saveCollapsedState(): void {
- if (isPlatformBrowser(this.platformId)) {
- try {
- localStorage.setItem(
- this.sidebarCollapsedKey,
- String(this.isCollapsed)
- );
- } catch (e) {
- console.error('Could not write to localStorage for sidebar state:', e);
- }
- }
+ // Der Service kümmert sich um die Serialisierung des booleans
+ this.storageService.setItem(this.sidebarCollapsedKey, this.isCollapsed);
}
}