From dab966b69e18d67476a5ad660f1dd63b2914b582 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Mon, 8 Sep 2025 16:21:08 +0200 Subject: [PATCH] auth --- .../validators/password-match.validator.ts | 10 ++ src/app/features/auth/_auth-common.css | 91 +++++++++++++++++++ src/app/features/auth/auth.module.ts | 9 +- src/app/features/auth/auth.routes.ts | 19 +++- .../auth-layout/auth-layout.component.css | 33 +++++++ .../auth-layout/auth-layout.component.html | 8 +- .../auth-layout/auth-layout.component.ts | 12 ++- .../forgot-password.component.css | 28 ++++++ .../forgot-password.component.html | 37 +++++++- .../forgot-password.component.ts | 32 ++++++- .../auth/components/login/login.component.css | 11 +++ .../components/login/login.component.html | 61 ++++++++++++- .../auth/components/login/login.component.ts | 31 ++++++- .../register/register.component.css | 8 ++ .../register/register.component.html | 40 +++++++- .../components/register/register.component.ts | 30 +++++- .../reset-password.component.css | 5 + .../reset-password.component.html | 22 ++++- .../reset-password.component.ts | 45 ++++++++- .../verify-email/verify-email.component.css | 22 +++++ .../verify-email/verify-email.component.html | 18 +++- .../verify-email/verify-email.component.ts | 8 +- src/styles.css | 3 +- 23 files changed, 545 insertions(+), 38 deletions(-) create mode 100644 src/app/core/validators/password-match.validator.ts create mode 100644 src/app/features/auth/_auth-common.css diff --git a/src/app/core/validators/password-match.validator.ts b/src/app/core/validators/password-match.validator.ts new file mode 100644 index 0000000..b4517fe --- /dev/null +++ b/src/app/core/validators/password-match.validator.ts @@ -0,0 +1,10 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; + +export const passwordMatchValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => { + const password = control.get('password'); + const confirmPassword = control.get('confirmPassword'); + + return password && confirmPassword && password.value !== confirmPassword.value + ? { passwordMismatch: true } + : null; +}; \ No newline at end of file diff --git a/src/app/features/auth/_auth-common.css b/src/app/features/auth/_auth-common.css new file mode 100644 index 0000000..cf5a677 --- /dev/null +++ b/src/app/features/auth/_auth-common.css @@ -0,0 +1,91 @@ +/* ================================================================================= + * GEMEINSAME STILE FÜR DAS AUTH-FEATURE + * ================================================================================= */ + +/* -- Globale Layout-Elemente -- */ +.component-header { + text-align: center; + margin-bottom: 2rem; +} +.auth-title { + font-size: 1.75rem; + font-weight: 700; + color: var(--color-text); + margin-bottom: 0.5rem; +} +.auth-subtitle { + color: var(--color-text-light); + font-size: 1rem; +} + +/* -- Formular-Basiselemente & Helfer -- */ +form { + display: flex; + flex-direction: column; + gap: 1.25rem; +} +.btn-full-width { + width: 100%; +} +.btn-icon-left { + display: flex; + justify-content: center; + align-items: center; + gap: 0.75rem; +} +.error-text { + color: var(--color-danger); + font-size: 0.85rem; + padding-top: 0.25rem; +} +.info-text { + color: var(--color-text-light); + text-align: center; + line-height: 1.6; +} + +/* -- Links & Footer -- */ +.link { + color: var(--color-primary); + font-weight: 500; + text-decoration: none; + font-size: 0.9rem; + transition: color var(--transition-speed); +} +.link:hover { + text-decoration: underline; + color: var(--color-primary-dark); +} +.footer-link { + text-align: center; + margin-top: 1.5rem; + color: var(--color-text-light); + font-size: 0.95rem; +} + +/* -- Visueller Trenner ("oder") -- */ +.divider-or { + display: flex; + align-items: center; + text-align: center; + color: var(--color-text-light); + font-size: 0.85rem; + font-weight: 500; + margin: 0.5rem 0; +} +.divider-or::before, +.divider-or::after { + content: ''; + flex: 1; + border-bottom: 1px solid var(--color-border); +} +.divider-or:not(:empty)::before { margin-right: 1em; } +.divider-or:not(:empty)::after { margin-left: 1em; } + +/* -- Autofill Fix -- */ +.form-input:-webkit-autofill ~ .form-label { + top: 0; + font-size: 0.8rem; + color: var(--color-primary); + border-radius: var(--border-radius-sm); +} \ No newline at end of file diff --git a/src/app/features/auth/auth.module.ts b/src/app/features/auth/auth.module.ts index 235b03f..7be5213 100644 --- a/src/app/features/auth/auth.module.ts +++ b/src/app/features/auth/auth.module.ts @@ -1,17 +1,10 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ReactiveFormsModule } from '@angular/forms'; - import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component'; import { LoginComponent } from './components/login/login.component'; @NgModule({ declarations: [], - imports: [ - CommonModule, - ReactiveFormsModule, - AuthLayoutComponent, - LoginComponent, - ], + imports: [CommonModule, AuthLayoutComponent, LoginComponent], }) export class AuthModule {} diff --git a/src/app/features/auth/auth.routes.ts b/src/app/features/auth/auth.routes.ts index 19d09a6..500bc7e 100644 --- a/src/app/features/auth/auth.routes.ts +++ b/src/app/features/auth/auth.routes.ts @@ -13,10 +13,19 @@ export const AUTH_ROUTES: Routes = [ component: AuthLayoutComponent, children: [ { path: '', redirectTo: 'login', pathMatch: 'full' }, + { path: 'login', component: LoginComponent, title: 'Anmelden' }, { path: 'register', component: RegisterComponent, title: 'Registrieren' }, - { path: 'forgot-password', component: ForgotPasswordComponent, title: 'Passwort vergessen' }, - { path: 'reset-password/:token', component: ResetPasswordComponent, title: 'Neues Passwort' } - ] - } -]; \ No newline at end of file + { + path: 'forgot-password', + component: ForgotPasswordComponent, + title: 'Passwort vergessen', + }, + { + path: 'reset-password/:token', + component: ResetPasswordComponent, + title: 'Neues Passwort', + }, + ], + }, +]; diff --git a/src/app/features/auth/components/auth-layout/auth-layout.component.css b/src/app/features/auth/components/auth-layout/auth-layout.component.css index e69de29..32e230d 100644 --- a/src/app/features/auth/components/auth-layout/auth-layout.component.css +++ b/src/app/features/auth/components/auth-layout/auth-layout.component.css @@ -0,0 +1,33 @@ +/* src\app\features\auth\components\auth-layout\auth-layout.component.css */ +/* Stile NUR für den äußeren Rahmen aller Auth-Seiten */ +:host { + display: block; + width: 100%; + min-height: 100vh; + background-color: var(--color-body-bg); +} + +.auth-container { + display: grid; + place-items: center; + min-height: 100vh; + padding: 1rem; +} + +.auth-card { + padding: 2rem; + width: 100%; + max-width: 450px; + animation: fade-in 0.5s ease-out forwards; +} + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/app/features/auth/components/auth-layout/auth-layout.component.html b/src/app/features/auth/components/auth-layout/auth-layout.component.html index afdb396..e79cc21 100644 --- a/src/app/features/auth/components/auth-layout/auth-layout.component.html +++ b/src/app/features/auth/components/auth-layout/auth-layout.component.html @@ -1 +1,7 @@ -

auth-layout works!

+
+
+
+ +
+
+
diff --git a/src/app/features/auth/components/auth-layout/auth-layout.component.ts b/src/app/features/auth/components/auth-layout/auth-layout.component.ts index 5d65cf7..58a46cf 100644 --- a/src/app/features/auth/components/auth-layout/auth-layout.component.ts +++ b/src/app/features/auth/components/auth-layout/auth-layout.component.ts @@ -1,9 +1,15 @@ import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-auth-layout', - imports: [], + imports: [ + RouterOutlet + ], templateUrl: './auth-layout.component.html', - styleUrl: './auth-layout.component.css', + styleUrl: './auth-layout.component.css' }) -export class AuthLayoutComponent {} +export class AuthLayoutComponent { + // Diese Komponente benötigt in der Regel keine eigene Logik. + // Sie dient nur als Hülle für die untergeordneten Routen. +} \ No newline at end of file diff --git a/src/app/features/auth/components/forgot-password/forgot-password.component.css b/src/app/features/auth/components/forgot-password/forgot-password.component.css index e69de29..ba9f4a5 100644 --- a/src/app/features/auth/components/forgot-password/forgot-password.component.css +++ b/src/app/features/auth/components/forgot-password/forgot-password.component.css @@ -0,0 +1,28 @@ +/* src\app\features\auth\components\forgot-password\forgot-password.component.css */ +@import '../../_auth-common.css'; + +/* Stile NUR für die Passwort-vergessen-Seite */ +:host { display: block; width: 100%; } + +form { + gap: 1.5rem; /* Etwas mehr Abstand als beim Login */ +} + +.info-text { + margin-bottom: 2rem; +} + +.success-message { + text-align: center; +} + +.success-title { + font-size: 1.25rem; + font-weight: 600; + color: var(--color-text); + margin-bottom: 0.75rem; +} + +.footer-link { + margin-top: 2rem; +} \ No newline at end of file diff --git a/src/app/features/auth/components/forgot-password/forgot-password.component.html b/src/app/features/auth/components/forgot-password/forgot-password.component.html index 3f85a0f..629df20 100644 --- a/src/app/features/auth/components/forgot-password/forgot-password.component.html +++ b/src/app/features/auth/components/forgot-password/forgot-password.component.html @@ -1 +1,36 @@ -

forgot-password works!

+ +
+

+ Geben Sie Ihre E-Mail-Adresse ein, und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts. +

+ +
+ +
+ + +
+ + +
+
+ + +
+

Prüfen Sie Ihr Postfach

+

+ Wenn ein Konto mit der E-Mail-Adresse {{ forgotPasswordForm.value.email }} existiert, haben wir soeben einen Link zum Zurücksetzen des Passworts dorthin gesendet. +

+
+ + + \ No newline at end of file diff --git a/src/app/features/auth/components/forgot-password/forgot-password.component.ts b/src/app/features/auth/components/forgot-password/forgot-password.component.ts index 3f3c834..1e544b6 100644 --- a/src/app/features/auth/components/forgot-password/forgot-password.component.ts +++ b/src/app/features/auth/components/forgot-password/forgot-password.component.ts @@ -1,11 +1,39 @@ import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'app-forgot-password', - imports: [], + imports: [ + CommonModule, + ReactiveFormsModule, + RouterLink + ], templateUrl: './forgot-password.component.html', styleUrl: './forgot-password.component.css' }) export class ForgotPasswordComponent { + forgotPasswordForm: FormGroup; + // Optional: Eine Eigenschaft, um eine Erfolgsmeldung anzuzeigen + emailSent = false; -} + constructor(private fb: FormBuilder) { + this.forgotPasswordForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]] + }); + } + + onSubmit() { + if (this.forgotPasswordForm.valid) { + // Hier würde die Logik zum Senden der E-Mail an das Backend stehen + console.log('Anforderung zum Zurücksetzen des Passworts für:', this.forgotPasswordForm.value.email); + // z.B. this.authService.sendPasswordResetEmail(this.forgotPasswordForm.value.email); + + // Setze den Zustand, um die Erfolgsmeldung anzuzeigen + this.emailSent = true; + } else { + this.forgotPasswordForm.markAllAsTouched(); + } + } +} \ No newline at end of file diff --git a/src/app/features/auth/components/login/login.component.css b/src/app/features/auth/components/login/login.component.css index e69de29..bb50aa7 100644 --- a/src/app/features/auth/components/login/login.component.css +++ b/src/app/features/auth/components/login/login.component.css @@ -0,0 +1,11 @@ +/* src\app\features\auth\components\login\login.component.css */ +@import '../../_auth-common.css'; + +/* Stile NUR für die Login-Seite */ +:host { display: block; width: 100%; } + +.form-actions { + text-align: right; + margin-top: -0.75rem; + margin-bottom: 0.5rem; +} \ No newline at end of file diff --git a/src/app/features/auth/components/login/login.component.html b/src/app/features/auth/components/login/login.component.html index 147cfc4..5a8300e 100644 --- a/src/app/features/auth/components/login/login.component.html +++ b/src/app/features/auth/components/login/login.component.html @@ -1 +1,60 @@ -

login works!

+
+

Anmelden

+

Starten Sie, indem Sie Ihre Daten eingeben.

+
+ + +
+ + +
+ + +
+ + + +
+ + +
+ + + + + + + + + +
+ oder +
+ + + + + + + +
\ No newline at end of file diff --git a/src/app/features/auth/components/login/login.component.ts b/src/app/features/auth/components/login/login.component.ts index b3f2e24..02291a4 100644 --- a/src/app/features/auth/components/login/login.component.ts +++ b/src/app/features/auth/components/login/login.component.ts @@ -1,11 +1,36 @@ import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +// WICHTIG: ReactiveFormsModule HIER importieren +import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'app-login', - imports: [], + standalone: true, + imports: [ + CommonModule, + RouterLink, + ReactiveFormsModule // <-- HIER IST DIE KORREKTUR. Jetzt kennt die Komponente [formGroup]. + ], templateUrl: './login.component.html', - styleUrl: './login.component.css' + styleUrl: './login.component.css', }) export class LoginComponent { + loginForm: FormGroup; -} + constructor(private fb: FormBuilder) { + this.loginForm = this.fb.group({ + email: ['', [Validators.required, Validators.email]], + password: ['', [Validators.required]], + }); + } + + onSubmit() { + if (this.loginForm.valid) { + console.log('Formular abgeschickt:', this.loginForm.value); + } else { + console.log('Formular ist ungültig.'); + this.loginForm.markAllAsTouched(); + } + } +} \ No newline at end of file diff --git a/src/app/features/auth/components/register/register.component.css b/src/app/features/auth/components/register/register.component.css index e69de29..50996d4 100644 --- a/src/app/features/auth/components/register/register.component.css +++ b/src/app/features/auth/components/register/register.component.css @@ -0,0 +1,8 @@ +/* src\app\features\auth\components\register\register.component.css */ +@import '../../_auth-common.css'; + +/* Stile NUR für die Registrierungs-Seite */ +:host { display: block; width: 100%; } + +/* Diese Komponente hat momentan keine spezifischen Extra-Stile, + aber die Datei ist da, falls welche benötigt werden. */ \ No newline at end of file diff --git a/src/app/features/auth/components/register/register.component.html b/src/app/features/auth/components/register/register.component.html index 6b0ba2e..fb3586a 100644 --- a/src/app/features/auth/components/register/register.component.html +++ b/src/app/features/auth/components/register/register.component.html @@ -1 +1,39 @@ -

register works!

+
+

Konto erstellen

+

Starten Sie, indem Sie Ihre Daten eingeben.

+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ Die Passwörter stimmen nicht überein. +
+
+ + +
+ + \ No newline at end of file diff --git a/src/app/features/auth/components/register/register.component.ts b/src/app/features/auth/components/register/register.component.ts index e3723b8..009411a 100644 --- a/src/app/features/auth/components/register/register.component.ts +++ b/src/app/features/auth/components/register/register.component.ts @@ -1,11 +1,37 @@ import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { RouterLink } from '@angular/router'; +// Optional: Ein Custom Validator für den Passwort-Vergleich +import { passwordMatchValidator } from '../../../../core/validators/password-match.validator'; @Component({ selector: 'app-register', - imports: [], + imports: [CommonModule, ReactiveFormsModule, RouterLink], templateUrl: './register.component.html', styleUrl: './register.component.css' }) export class RegisterComponent { + registerForm: FormGroup; -} + constructor(private fb: FormBuilder) { + this.registerForm = this.fb.group({ + fullName: ['', [Validators.required]], + email: ['', [Validators.required, Validators.email]], + password: ['', [Validators.required, Validators.minLength(8)]], + confirmPassword: ['', [Validators.required]] + }, { + validators: passwordMatchValidator // Custom Validator auf Formular-Ebene + }); + } + + onSubmit() { + if (this.registerForm.valid) { + console.log('Registrierungsdaten:', this.registerForm.value); + // z.B. this.authService.register(this.registerForm.value); + } else { + this.registerForm.markAllAsTouched(); + console.log('Registrierungsformular ist ungültig.'); + } + } +} \ No newline at end of file diff --git a/src/app/features/auth/components/reset-password/reset-password.component.css b/src/app/features/auth/components/reset-password/reset-password.component.css index e69de29..f191383 100644 --- a/src/app/features/auth/components/reset-password/reset-password.component.css +++ b/src/app/features/auth/components/reset-password/reset-password.component.css @@ -0,0 +1,5 @@ +/* src\app\features\auth\components\register\register.component.css */ +@import '../../_auth-common.css'; + +/* Stile NUR für die Passwort-zurücksetzen-Seite */ +:host { display: block; width: 100%; } \ No newline at end of file diff --git a/src/app/features/auth/components/reset-password/reset-password.component.html b/src/app/features/auth/components/reset-password/reset-password.component.html index 66175bb..d2ae5d0 100644 --- a/src/app/features/auth/components/reset-password/reset-password.component.html +++ b/src/app/features/auth/components/reset-password/reset-password.component.html @@ -1 +1,21 @@ -

reset-password works!

+
+

Neues Passwort festlegen

+

Bitte geben Sie Ihr neues, sicheres Passwort ein.

+
+ +
+
+ + +
+
+ + +
+ Die Passwörter stimmen nicht überein. +
+
+ +
\ No newline at end of file diff --git a/src/app/features/auth/components/reset-password/reset-password.component.ts b/src/app/features/auth/components/reset-password/reset-password.component.ts index d84e75e..08ce4d3 100644 --- a/src/app/features/auth/components/reset-password/reset-password.component.ts +++ b/src/app/features/auth/components/reset-password/reset-password.component.ts @@ -1,11 +1,48 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms'; +import { Router, ActivatedRoute } from '@angular/router'; +import { passwordMatchValidator } from '../../../../core/validators/password-match.validator'; @Component({ selector: 'app-reset-password', - imports: [], + standalone: true, + imports: [CommonModule, ReactiveFormsModule], templateUrl: './reset-password.component.html', styleUrl: './reset-password.component.css' }) -export class ResetPasswordComponent { +export class ResetPasswordComponent implements OnInit { + resetPasswordForm: FormGroup; + token: string | null = null; + + constructor( + private fb: FormBuilder, + private route: ActivatedRoute, + private router: Router + ) { + this.resetPasswordForm = this.fb.group({ + password: ['', [Validators.required, Validators.minLength(8)]], + confirmPassword: ['', [Validators.required]] + }, { validators: passwordMatchValidator }); + } -} + ngOnInit(): void { + // Liest den Token aus der URL-Parameter + this.token = this.route.snapshot.paramMap.get('token'); + if (!this.token) { + console.error('Kein Token gefunden!'); + // Optional: zum Login weiterleiten, wenn kein Token da ist + this.router.navigate(['/auth/login']); + } + } + + onSubmit() { + if (this.resetPasswordForm.valid) { + console.log('Neues Passwort wird gesetzt mit Token:', this.token); + console.log('Formulardaten:', this.resetPasswordForm.value); + // z.B. this.authService.resetPassword(this.token, this.resetPasswordForm.value.password); + } else { + this.resetPasswordForm.markAllAsTouched(); + } + } +} \ No newline at end of file diff --git a/src/app/features/auth/components/verify-email/verify-email.component.css b/src/app/features/auth/components/verify-email/verify-email.component.css index e69de29..fe46f8a 100644 --- a/src/app/features/auth/components/verify-email/verify-email.component.css +++ b/src/app/features/auth/components/verify-email/verify-email.component.css @@ -0,0 +1,22 @@ +/* src\app\features\auth\components\register\register.component.css */ +@import '../../_auth-common.css'; + +/* Stile NUR für die E-Mail-verifizieren-Seite */ +:host { display: block; width: 100%; } + +.verify-content { + text-align: center; +} +.icon { + color: var(--color-primary); + margin-bottom: 1.5rem; +} +.info-text { + margin-bottom: 1rem; +} +.info-text.small { + font-size: 0.9rem; +} +.footer-link { + margin-top: 2rem; +} \ No newline at end of file diff --git a/src/app/features/auth/components/verify-email/verify-email.component.html b/src/app/features/auth/components/verify-email/verify-email.component.html index c39c8c0..acc16de 100644 --- a/src/app/features/auth/components/verify-email/verify-email.component.html +++ b/src/app/features/auth/components/verify-email/verify-email.component.html @@ -1 +1,17 @@ -

verify-email works!

+
+

Bestätigen Sie Ihre E-Mail

+
+ +
+ +

+ Ein Bestätigungslink wurde an Ihre E-Mail-Adresse gesendet. Bitte klicken Sie darauf, um Ihr Konto zu aktivieren. +

+

+ Keine E-Mail erhalten? Erneut senden. +

+
+ + \ No newline at end of file diff --git a/src/app/features/auth/components/verify-email/verify-email.component.ts b/src/app/features/auth/components/verify-email/verify-email.component.ts index 4215940..089c793 100644 --- a/src/app/features/auth/components/verify-email/verify-email.component.ts +++ b/src/app/features/auth/components/verify-email/verify-email.component.ts @@ -1,11 +1,13 @@ import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; @Component({ selector: 'app-verify-email', - imports: [], + standalone: true, + imports: [RouterLink], templateUrl: './verify-email.component.html', styleUrl: './verify-email.component.css' }) export class VerifyEmailComponent { - -} + // Diese Komponente ist oft rein statisch und benötigt keine komplexe Logik +} \ No newline at end of file diff --git a/src/styles.css b/src/styles.css index 87f2a03..ecf4d61 100644 --- a/src/styles.css +++ b/src/styles.css @@ -349,6 +349,7 @@ body.dark-theme .btn-icon-danger:hover { padding: 0 0.25rem; transition: all 0.2s ease-out; pointer-events: none; + border-radius: var(--border-radius-sm); } .form-input:focus ~ .form-label, .form-input:not(:placeholder-shown) ~ .form-label { @@ -996,8 +997,6 @@ body.dark-theme .dialog-icon-container { background-color: var(--color-body-bg); } - - .menu-container { position: relative; display: inline-block;