localstorage service u. auslagerung
This commit is contained in:
1
public/icons/filter.svg
Normal file
1
public/icons/filter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#666666"><path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/></svg>
|
||||
|
After Width: | Height: | Size: 290 B |
1
public/icons/menu.svg
Normal file
1
public/icons/menu.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#666666"><path d="M120-240v-80h720v80H120Zm0-200v-80h720v80H120Zm0-200v-80h720v80H120Z"/></svg>
|
||||
|
After Width: | Height: | Size: 193 B |
1
public/icons/plus.svg
Normal file
1
public/icons/plus.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#666666"><path d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 182 B |
@@ -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<ConsentStatus>(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.');
|
||||
}
|
||||
}
|
||||
@@ -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<boolean>(this.isBrowser() && !!this.getToken());
|
||||
private loggedInStatus = new BehaviorSubject<boolean>(!!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<string>(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<string[]>(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<DecodedToken>(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();
|
||||
}
|
||||
}
|
||||
87
src/app/core/services/storage.service.ts
Normal file
87
src/app/core/services/storage.service.ts
Normal file
@@ -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<T>(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<T>(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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<boolean>(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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user