refactor - login und auth
This commit is contained in:
@@ -1,34 +1,42 @@
|
||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
// /src/app/app.config.ts
|
||||
|
||||
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||
|
||||
import { provideHttpClient, withFetch } from '@angular/common/http';
|
||||
import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
|
||||
import { provideClientHydration } from '@angular/platform-browser';
|
||||
|
||||
// +++ HIER IST DIE DEFINITIVE KORREKTUR FÜR FORMS-PROVIDER +++
|
||||
import {
|
||||
ReactiveFormsModule, // <-- Importieren des Moduls selbst
|
||||
FormsModule // <-- Importieren des Moduls für Template-driven forms (falls benötigt)
|
||||
} from '@angular/forms';
|
||||
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
// --- NEUE IMPORTE ---
|
||||
import { environment } from './environment';
|
||||
import { API_URL } from './core/tokens/api-url.token';
|
||||
import { authInterceptor } from './core/interceptors/auth.interceptor';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideAnimations(),
|
||||
|
||||
provideHttpClient(withFetch()),
|
||||
provideClientHydration(),
|
||||
provideClientHydration(),
|
||||
|
||||
// +++ Korrigierte Verwendung der Forms-Provider +++
|
||||
// Dies macht die Provider für Reactive Forms global verfügbar
|
||||
ReactiveFormsModule.withConfig({
|
||||
warnOnNgModelWithFormControl: 'never' // Optional: Schaltet eine NgModel Warnung aus
|
||||
}).providers!, // <--- Korrigiert
|
||||
|
||||
// Falls Sie Template-Driven Forms (mit NgModel) benötigen, würden Sie das hinzufügen:
|
||||
// FormsModule.withConfig({}).providers!
|
||||
// --- HttpClient Konfiguration aktualisieren ---
|
||||
// withFetch() entfernen, da es mit Interceptors noch Probleme geben kann
|
||||
// Stattdessen withInterceptors() verwenden
|
||||
provideHttpClient(
|
||||
withInterceptors([authInterceptor]) // Registriert unseren neuen Interceptor
|
||||
),
|
||||
|
||||
// --- Forms Provider (sauberer Import) ---
|
||||
// importProvidersFrom ist der empfohlene Weg für NgModule-basierte Bibliotheken
|
||||
importProvidersFrom(
|
||||
ReactiveFormsModule.withConfig({ warnOnNgModelWithFormControl: 'never' }),
|
||||
FormsModule
|
||||
),
|
||||
|
||||
// --- API_URL Provider hinzufügen ---
|
||||
// Stellt den API_URL-Token mit dem Wert aus der passenden environment-Datei bereit
|
||||
{ provide: API_URL, useValue: environment.apiUrl }
|
||||
]
|
||||
};
|
||||
@@ -4,8 +4,6 @@ import { AccessDeniedComponent } from './core/components/access-denied/access-de
|
||||
import { NotFoundComponent } from './core/components/not-found/not-found.component';
|
||||
|
||||
import { DefaultLayoutComponent } from './core/components/default-layout/default-layout.component';
|
||||
|
||||
import { DashboardPageComponent } from './features/components/dashboard/dashboard-page/dashboard-page.component';
|
||||
import { authGuard } from './core/guards/auth.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
@@ -18,8 +16,8 @@ export const routes: Routes = [
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: DefaultLayoutComponent,
|
||||
// canActivate: [authGuard],
|
||||
// data: { requiredRole: 'Admin' },
|
||||
canActivate: [authGuard],
|
||||
data: { requiredRole: 'Admin' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
|
||||
8
src/app/core/components/core.routes.ts
Normal file
8
src/app/core/components/core.routes.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
// Importiere dein spezielles Layout für Auth-Seiten und alle Komponenten
|
||||
|
||||
export const CORE_ROUTES: Routes = [
|
||||
|
||||
|
||||
];
|
||||
@@ -1,48 +1,43 @@
|
||||
// /src/app/core/guards/auth.guard.ts
|
||||
|
||||
import { inject } from '@angular/core';
|
||||
import { CanActivateFn, Router, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { CanActivateFn, Router, ActivatedRouteSnapshot, UrlTree } from '@angular/router';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
|
||||
/**
|
||||
* Ein funktionaler Guard, der Routen schützt.
|
||||
*
|
||||
* Ein funktionaler Guard, der Routen basierend auf Authentifizierung und Rollen schützt.
|
||||
*
|
||||
* Verwendung in der Routing-Konfiguration:
|
||||
* {
|
||||
* path: 'admin',
|
||||
* component: AdminDashboardComponent,
|
||||
* canActivate: [authGuard],
|
||||
* data: { requiredRole: 'Admin' } // Die erforderliche Rolle hier übergeben
|
||||
* }
|
||||
* {
|
||||
* path: 'profile',
|
||||
* component: UserProfileComponent,
|
||||
* canActivate: [authGuard] // Schützt nur vor nicht angemeldeten Benutzern
|
||||
* }
|
||||
*/
|
||||
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
|
||||
export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot): boolean | UrlTree => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
// 1. Prüfen, ob der Benutzer überhaupt angemeldet ist
|
||||
if (!authService.getToken()) {
|
||||
// Nicht angemeldet -> zum Login umleiten
|
||||
router.navigate(['/login']);
|
||||
return false;
|
||||
}
|
||||
const isLoggedIn = !!authService.getToken();
|
||||
const requiredRole = route.data['requiredRole'] as string | undefined;
|
||||
|
||||
// 2. Prüfen, ob die Route eine bestimmte Rolle erfordert
|
||||
const requiredRole = route.data['requiredRole'] as string;
|
||||
if (requiredRole) {
|
||||
// Rolle ist erforderlich -> prüfen, ob der Benutzer sie hat
|
||||
if (authService.hasRole(requiredRole)) {
|
||||
// Benutzer hat die Rolle -> Zugriff erlaubt
|
||||
// Fall 1: Route ist nicht geschützt oder der Benutzer ist angemeldet und hat die richtige Rolle
|
||||
if (isLoggedIn) {
|
||||
if (!requiredRole || authService.hasRole(requiredRole)) {
|
||||
// Zugriff erlaubt
|
||||
return true;
|
||||
} else {
|
||||
// Benutzer hat die Rolle nicht -> zu einer "Zugriff verweigert"-Seite umleiten
|
||||
router.navigate(['/access-denied']);
|
||||
return false;
|
||||
// Angemeldet, aber falsche Rolle -> Zugriff verweigert
|
||||
// Leite zu einer 'Forbidden'-Seite um oder zur Startseite
|
||||
return router.createUrlTree(['/forbidden']);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Wenn die Route nur eine Anmeldung, aber keine spezielle Rolle erfordert
|
||||
return true;
|
||||
// Fall 2: Nicht angemeldet
|
||||
// Leite zur Login-Seite um und speichere die Ziel-URL für eine spätere Weiterleitung
|
||||
return router.createUrlTree(['/auth/login'], { queryParams: { returnUrl: route.url.join('/') } });
|
||||
};
|
||||
30
src/app/core/interceptors/auth.interceptor.ts
Normal file
30
src/app/core/interceptors/auth.interceptor.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// /src/app/core/interceptors/auth.interceptor.ts
|
||||
|
||||
import { HttpInterceptorFn, HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http';
|
||||
import { inject } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { API_URL } from '../tokens/api-url.token';
|
||||
|
||||
export const authInterceptor: HttpInterceptorFn = (
|
||||
req: HttpRequest<unknown>,
|
||||
next: HttpHandlerFn
|
||||
): Observable<HttpEvent<unknown>> => {
|
||||
|
||||
const authService = inject(AuthService);
|
||||
const apiUrl = inject(API_URL);
|
||||
const token = authService.getToken();
|
||||
|
||||
// Den Interceptor nur auf Anfragen an unsere eigene API anwenden
|
||||
if (token && req.url.startsWith(apiUrl)) {
|
||||
// Request klonen und den Authorization-Header hinzufügen
|
||||
const clonedReq = req.clone({
|
||||
headers: req.headers.set('Authorization', `Bearer ${token}`),
|
||||
});
|
||||
return next(clonedReq);
|
||||
}
|
||||
|
||||
// Für alle anderen Anfragen (oder wenn kein Token vorhanden ist),
|
||||
// den ursprünglichen Request weiterleiten.
|
||||
return next(req);
|
||||
};
|
||||
@@ -1,15 +1,47 @@
|
||||
// src/app/core/models/auth.models.ts
|
||||
// /src/app/core/models/auth.model.ts
|
||||
|
||||
export interface LoginRequestDto {
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface AuthResponseDto {
|
||||
export interface RegisterRequest extends LoginRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface AuthResponse {
|
||||
isAuthSuccessful: boolean;
|
||||
errorMessage?: string;
|
||||
token?: string;
|
||||
userId?: string;
|
||||
email?: string;
|
||||
roles?: string[];
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
confirmNewPassword: string;
|
||||
}
|
||||
|
||||
export interface ChangeEmailRequest {
|
||||
newEmail: string;
|
||||
currentPassword: string;
|
||||
}
|
||||
|
||||
export interface ForgotPasswordRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface ResetPassword {
|
||||
email: string;
|
||||
token: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
export interface ResendEmailConfirmationRequest {
|
||||
email: string;
|
||||
}
|
||||
@@ -1,121 +1,96 @@
|
||||
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
|
||||
// /src/app/core/services/auth.service.ts
|
||||
|
||||
import { Injectable, PLATFORM_ID, 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 { tap, catchError, map } from 'rxjs/operators';
|
||||
|
||||
import { LoginRequestDto } from '../models/auth.models';
|
||||
import { AuthResponseDto } from '../models/auth.models';
|
||||
import { LoginRequest, AuthResponse, RegisterRequest } from '../models/auth.models'
|
||||
import { API_URL } from '../tokens/api-url.token';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
// Basis-URL Ihrer API
|
||||
private apiUrl = '/api/v1/Auth';
|
||||
|
||||
// Keys für die Speicherung im localStorage
|
||||
private readonly tokenKey = 'auth-token';
|
||||
private readonly userRolesKey = 'auth-user-roles';
|
||||
// --- Injizierte Abhängigkeiten mit moderner Syntax ---
|
||||
private http = inject(HttpClient);
|
||||
private router = inject(Router);
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
private apiUrl = inject(API_URL); // <-- SAUBERE INJEKTION!
|
||||
|
||||
// Ein BehaviorSubject, um den Login-Status reaktiv in der App zu teilen
|
||||
private loggedInStatus = new BehaviorSubject<boolean>(this.hasToken());
|
||||
private readonly endpoint = '/Auth';
|
||||
|
||||
// Keys für die Speicherung im localStorage
|
||||
private readonly TOKEN_KEY = 'auth-token';
|
||||
private readonly ROLES_KEY = 'auth-user-roles';
|
||||
|
||||
private loggedInStatus = new BehaviorSubject<boolean>(this.isBrowser() && !!this.getToken());
|
||||
public isLoggedIn$ = this.loggedInStatus.asObservable();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private router: Router,
|
||||
@Inject(PLATFORM_ID) private platformId: Object
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Meldet einen Admin-Benutzer an.
|
||||
*/
|
||||
loginAdmin(credentials: LoginRequestDto): Observable<AuthResponseDto> {
|
||||
return this.http.post<AuthResponseDto>(`${this.apiUrl}/login/admin`, credentials).pipe(
|
||||
tap(response => {
|
||||
if (response.isAuthSuccessful && response.token) {
|
||||
this.setSession(response);
|
||||
}
|
||||
}),
|
||||
catchError(error => {
|
||||
// Fehlerbehandlung, z.B. das Token löschen, falls eines vorhanden war
|
||||
this.logout();
|
||||
throw error;
|
||||
loginAdmin(credentials: LoginRequest): Observable<AuthResponse | null> {
|
||||
return this.http.post<AuthResponse>(`${this.apiUrl}${this.endpoint}/login/admin`, credentials).pipe(
|
||||
tap(response => this.setSession(response)),
|
||||
catchError(() => {
|
||||
this.clearSession(); // Bei fehlgeschlagenem Login immer die Session aufräumen
|
||||
return of(null); // Gib ein "leeres" Observable zurück statt einen Fehler zu werfen
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldet einen normalen Kunden an.
|
||||
*/
|
||||
loginCustomer(credentials: LoginRequestDto): Observable<AuthResponseDto> {
|
||||
return this.http.post<AuthResponseDto>(`${this.apiUrl}/login/customer`, credentials).pipe(
|
||||
tap(response => {
|
||||
if (response.isAuthSuccessful && response.token) {
|
||||
this.setSession(response);
|
||||
}
|
||||
loginCustomer(credentials: LoginRequest): Observable<AuthResponse | null> {
|
||||
return this.http.post<AuthResponse>(`${this.apiUrl}${this.endpoint}/login/customer`, credentials).pipe(
|
||||
tap(response => this.setSession(response)),
|
||||
catchError(() => {
|
||||
this.clearSession();
|
||||
return of(null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldet den Benutzer ab, entfernt die Daten aus dem Speicher und leitet um.
|
||||
*/
|
||||
register(data: RegisterRequest): Observable<{ message: string } | null> {
|
||||
return this.http.post<{ message: string }>(`${this.apiUrl}${this.endpoint}/register`, data).pipe(
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
localStorage.removeItem(this.tokenKey);
|
||||
localStorage.removeItem(this.userRolesKey);
|
||||
}
|
||||
this.loggedInStatus.next(false);
|
||||
this.router.navigate(['/login']); // oder wohin auch immer Sie umleiten möchten
|
||||
this.clearSession();
|
||||
this.router.navigate(['/auth/login']); // Empfehlung: Routen gruppieren
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt das gespeicherte JWT-Token zurück.
|
||||
*/
|
||||
getToken(): string | null {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
return localStorage.getItem(this.tokenKey);
|
||||
}
|
||||
return null;
|
||||
return this.isBrowser() ? localStorage.getItem(this.TOKEN_KEY) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Rollen des angemeldeten Benutzers zurück.
|
||||
*/
|
||||
getUserRoles(): string[] {
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
const roles = localStorage.getItem(this.userRolesKey);
|
||||
return roles ? JSON.parse(roles) : [];
|
||||
}
|
||||
return [];
|
||||
if (!this.isBrowser()) return [];
|
||||
const roles = localStorage.getItem(this.ROLES_KEY);
|
||||
return roles ? JSON.parse(roles) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Überprüft, ob der Benutzer eine bestimmte Rolle hat.
|
||||
*/
|
||||
hasRole(requiredRole: string): boolean {
|
||||
const userRoles = this.getUserRoles();
|
||||
return userRoles.includes(requiredRole);
|
||||
return this.getUserRoles().includes(requiredRole);
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Methode zum Speichern der Sitzungsdaten.
|
||||
*/
|
||||
private setSession(authResponse: AuthResponseDto): void {
|
||||
if (isPlatformBrowser(this.platformId) && authResponse.token && authResponse.roles) {
|
||||
localStorage.setItem(this.tokenKey, authResponse.token);
|
||||
localStorage.setItem(this.userRolesKey, JSON.stringify(authResponse.roles));
|
||||
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));
|
||||
this.loggedInStatus.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private Methode, die prüft, ob ein Token vorhanden ist (nützlich beim App-Start).
|
||||
*/
|
||||
private hasToken(): boolean {
|
||||
return !!this.getToken();
|
||||
private clearSession(): void {
|
||||
if (this.isBrowser()) {
|
||||
localStorage.removeItem(this.TOKEN_KEY);
|
||||
localStorage.removeItem(this.ROLES_KEY);
|
||||
}
|
||||
this.loggedInStatus.next(false);
|
||||
}
|
||||
|
||||
private isBrowser(): boolean {
|
||||
return isPlatformBrowser(this.platformId);
|
||||
}
|
||||
}
|
||||
48
src/app/core/services/logging.service.ts
Normal file
48
src/app/core/services/logging.service.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// /src/app/core/services/logging.service.ts
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { environment } from '../../environment';
|
||||
|
||||
export enum LogLevel {
|
||||
Info,
|
||||
Warn,
|
||||
Error
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LoggingService {
|
||||
|
||||
log(level: LogLevel, message: string, data?: any) {
|
||||
// In der Produktion werden nur Warnungen und Fehler geloggt
|
||||
if (!environment.production || level > LogLevel.Info) {
|
||||
const logEntry = `[${LogLevel[level]}] ${new Date().toISOString()}: ${message}`;
|
||||
|
||||
switch (level) {
|
||||
case LogLevel.Info:
|
||||
console.info(logEntry, data || '');
|
||||
break;
|
||||
case LogLevel.Warn:
|
||||
console.warn(logEntry, data || '');
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
console.error(logEntry, data || '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
// HIER KÖNNTE SPÄTER EINE LOGIK ZUM SENDEN AN EINEN SERVER STEHEN
|
||||
}
|
||||
|
||||
info(message: string, data?: any) {
|
||||
this.log(LogLevel.Info, message, data);
|
||||
}
|
||||
|
||||
warn(message: string, data?: any) {
|
||||
this.log(LogLevel.Warn, message, data);
|
||||
}
|
||||
|
||||
error(message: string, data?: any) {
|
||||
this.log(LogLevel.Error, message, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.error-message-container {
|
||||
width: 100%;
|
||||
background-color: #f8d7da; /* Helles Rot, anpassbar an dein Design */
|
||||
border: 1px solid #f5c6cb; /* Dunkleres Rot */
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem; /* Abstand zum Button */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #721c24; /* Dunkelroter Text */
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
<!-- /src/app/features/components/auth/login/login.component.html -->
|
||||
|
||||
<div class="component-header">
|
||||
<h2 class="auth-title">Anmelden</h2>
|
||||
<p class="auth-subtitle">Bitte geben Sie Ihre Daten ein, um fortzufahren.</p>
|
||||
</div>
|
||||
|
||||
<!-- novalidate verhindert die Standard-Browser-Validierung, sodass unser Angular-Feedback die volle Kontrolle hat -->
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
|
||||
<app-form-field
|
||||
label="E-Mail-Adresse"
|
||||
@@ -20,11 +23,18 @@
|
||||
<a routerLink="/auth/forgot-password" class="link">Passwort vergessen?</a>
|
||||
</div>
|
||||
|
||||
<!-- +++ NEU: Container für Fehlermeldungen +++ -->
|
||||
<div *ngIf="errorMessage" class="error-message-container">
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
</div>
|
||||
<!-- +++ ENDE NEU +++ -->
|
||||
|
||||
<app-button
|
||||
submitType="submit"
|
||||
buttonType="primary"
|
||||
[fullWidth]="true"
|
||||
[disabled]="loginForm.invalid">
|
||||
[disabled]="loginForm.invalid || isLoading"
|
||||
[isLoading]="isLoading">
|
||||
Anmelden
|
||||
</app-button>
|
||||
|
||||
@@ -33,7 +43,8 @@
|
||||
<app-button
|
||||
buttonType="secondary"
|
||||
[fullWidth]="true"
|
||||
iconName="placeholder">
|
||||
iconName="placeholder"
|
||||
[disabled]="isLoading">
|
||||
Mit Google anmelden
|
||||
</app-button>
|
||||
</form>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
ReactiveFormsModule,
|
||||
FormBuilder,
|
||||
Validators,
|
||||
FormGroup,
|
||||
} from '@angular/forms';
|
||||
import { RouterLink } from '@angular/router';
|
||||
// /src/app/features/components/auth/login/login.component.ts
|
||||
|
||||
// Importieren der wiederverwendbaren Komponenten
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { finalize } from 'rxjs';
|
||||
|
||||
// Services
|
||||
import { AuthService } from '../../../../core/services/auth.service';
|
||||
import { LoggingService } from '../../../../core/services/logging.service';
|
||||
|
||||
// UI Komponenten
|
||||
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||
import { LoginRequest } from '../../../../core/models/auth.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
@@ -27,22 +30,55 @@ import { FormFieldComponent } from '../../../../shared/components/form/form-fiel
|
||||
})
|
||||
export class LoginComponent {
|
||||
loginForm: FormGroup;
|
||||
isLoading = false;
|
||||
errorMessage: string | null = null;
|
||||
|
||||
|
||||
// Moderne Dependency Injection mit inject()
|
||||
private fb = inject(FormBuilder);
|
||||
private authService = inject(AuthService);
|
||||
private router = inject(Router);
|
||||
private logger = inject(LoggingService);
|
||||
|
||||
constructor(private fb: FormBuilder) {
|
||||
constructor() {
|
||||
this.loginForm = this.fb.group({
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: ['', [Validators.required]],
|
||||
email: ['admin@yourwebshop.com', [Validators.required, Validators.email]],
|
||||
password: ['SecureAdminPass123!', [Validators.required]],
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.loginForm.valid) {
|
||||
console.log('Formular abgeschickt:', this.loginForm.value);
|
||||
} else {
|
||||
console.log('Formular ist ungültig.');
|
||||
// Formular erneut prüfen und mehrfaches Absenden verhindern
|
||||
if (this.loginForm.invalid || this.isLoading) {
|
||||
this.loginForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isLoading = true;
|
||||
this.errorMessage = null;
|
||||
|
||||
const credentials: LoginRequest = this.loginForm.value;
|
||||
|
||||
// Wir rufen den zentralen AuthService auf
|
||||
this.authService.loginAdmin(credentials).pipe(
|
||||
// finalize wird immer ausgeführt, egal ob erfolgreich oder nicht
|
||||
finalize(() => this.isLoading = false)
|
||||
).subscribe({
|
||||
next: (response) => {
|
||||
if (response && response.isAuthSuccessful) {
|
||||
this.logger.info('Admin login successful', { email: credentials.email });
|
||||
// Erfolgreich eingeloggt -> Weiterleiten zum Admin-Dashboard
|
||||
this.router.navigate(['/dashboard']); // Passe die Route ggf. an
|
||||
} else {
|
||||
// Login fehlgeschlagen (falsches Passwort etc.), vom Backend kontrolliert
|
||||
this.errorMessage = 'E-Mail oder Passwort ist ungültig.';
|
||||
this.logger.warn('Admin login failed: Invalid credentials', { email: credentials.email });
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
// Unerwarteter Fehler (z.B. Server nicht erreichbar)
|
||||
this.errorMessage = 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.';
|
||||
this.logger.error('An unexpected error occurred during admin login', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,3 +134,42 @@
|
||||
visibility: visible;
|
||||
transform: translateX(-50%) translateY(-12px);
|
||||
}
|
||||
|
||||
|
||||
.btn.is-loading {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.btn-content.is-hidden {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Der Lade-Spinner */
|
||||
.spinner {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: #fff; /* Farbe des sich drehenden Teils */
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
/* Spinner-Farbe für nicht-primäre Buttons anpassen */
|
||||
.btn-secondary .spinner,
|
||||
.btn-stroked .spinner,
|
||||
.btn-flat .spinner,
|
||||
.btn-icon .spinner {
|
||||
border: 2px solid rgba(0, 0, 0, 0.1);
|
||||
border-top-color: var(--color-primary);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<!-- /src/app/shared/components/ui/button/button.component.html -->
|
||||
|
||||
<button
|
||||
[type]="submitType"
|
||||
[disabled]="disabled"
|
||||
[disabled]="disabled || isLoading"
|
||||
class="btn"
|
||||
[class.btn-primary]="buttonType === 'primary'"
|
||||
[class.btn-secondary]="buttonType === 'secondary'"
|
||||
@@ -9,7 +11,14 @@
|
||||
[class.btn-icon]="buttonType === 'icon' || buttonType === 'icon-danger'"
|
||||
[class.btn-icon-danger]="buttonType === 'icon-danger'"
|
||||
[class.btn-full-width]="fullWidth"
|
||||
[class.is-loading]="isLoading"
|
||||
>
|
||||
<app-icon *ngIf="iconName" [iconName]="iconName" [svgColor]="svgColor"></app-icon>
|
||||
<ng-content></ng-content>
|
||||
</button>
|
||||
<div *ngIf="isLoading" class="spinner"></div>
|
||||
|
||||
<div class="btn-content" [class.is-hidden]="isLoading">
|
||||
<app-icon *ngIf="iconName" [iconName]="iconName" [svgColor]="svgColor"></app-icon>
|
||||
<span>
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
@@ -19,6 +19,7 @@ export class ButtonComponent {
|
||||
@Input() fullWidth = false;
|
||||
@Input() iconName: string | null = null;
|
||||
@Input() svgColor: string | null = null;
|
||||
@Input() isLoading = false;
|
||||
|
||||
// Der Tooltip-Input
|
||||
@Input() tooltip: string | null = null;
|
||||
|
||||
Reference in New Issue
Block a user