auth
This commit is contained in:
10
src/app/core/validators/password-match.validator.ts
Normal file
10
src/app/core/validators/password-match.validator.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
91
src/app/features/auth/_auth-common.css
Normal file
91
src/app/features/auth/_auth-common.css
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
|
||||||
import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component';
|
import { AuthLayoutComponent } from './components/auth-layout/auth-layout.component';
|
||||||
import { LoginComponent } from './components/login/login.component';
|
import { LoginComponent } from './components/login/login.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [],
|
declarations: [],
|
||||||
imports: [
|
imports: [CommonModule, AuthLayoutComponent, LoginComponent],
|
||||||
CommonModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
AuthLayoutComponent,
|
|
||||||
LoginComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -13,10 +13,19 @@ export const AUTH_ROUTES: Routes = [
|
|||||||
component: AuthLayoutComponent,
|
component: AuthLayoutComponent,
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
{ path: '', redirectTo: 'login', pathMatch: 'full' },
|
||||||
|
|
||||||
{ path: 'login', component: LoginComponent, title: 'Anmelden' },
|
{ path: 'login', component: LoginComponent, title: 'Anmelden' },
|
||||||
{ path: 'register', component: RegisterComponent, title: 'Registrieren' },
|
{ path: 'register', component: RegisterComponent, title: 'Registrieren' },
|
||||||
{ path: 'forgot-password', component: ForgotPasswordComponent, title: 'Passwort vergessen' },
|
{
|
||||||
{ path: 'reset-password/:token', component: ResetPasswordComponent, title: 'Neues Passwort' }
|
path: 'forgot-password',
|
||||||
]
|
component: ForgotPasswordComponent,
|
||||||
}
|
title: 'Passwort vergessen',
|
||||||
];
|
},
|
||||||
|
{
|
||||||
|
path: 'reset-password/:token',
|
||||||
|
component: ResetPasswordComponent,
|
||||||
|
title: 'Neues Passwort',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,7 @@
|
|||||||
<p>auth-layout works!</p>
|
<div class="auth-container">
|
||||||
|
<div class="auth-card card">
|
||||||
|
<div class="auth-content">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterOutlet } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth-layout',
|
selector: 'app-auth-layout',
|
||||||
imports: [],
|
imports: [
|
||||||
|
RouterOutlet
|
||||||
|
],
|
||||||
templateUrl: './auth-layout.component.html',
|
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.
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1 +1,36 @@
|
|||||||
<p>forgot-password works!</p>
|
<!-- Ansicht, BEVOR das Formular abgeschickt wurde -->
|
||||||
|
<div *ngIf="!emailSent">
|
||||||
|
<p class="info-text">
|
||||||
|
Geben Sie Ihre E-Mail-Adresse ein, und wir senden Ihnen einen Link zum Zurücksetzen Ihres Passworts.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
|
<!-- E-Mail-Feld -->
|
||||||
|
<div class="form-field">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-input"
|
||||||
|
id="email"
|
||||||
|
placeholder=" "
|
||||||
|
formControlName="email">
|
||||||
|
<label for="email" class="form-label">E-Mail-Adresse</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="forgotPasswordForm.invalid">
|
||||||
|
Link zum Zurücksetzen anfordern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ansicht, NACHDEM das Formular erfolgreich abgeschickt wurde -->
|
||||||
|
<div *ngIf="emailSent" class="success-message">
|
||||||
|
<h3 class="success-title">Prüfen Sie Ihr Postfach</h3>
|
||||||
|
<p class="info-text">
|
||||||
|
Wenn ein Konto mit der E-Mail-Adresse <strong>{{ forgotPasswordForm.value.email }}</strong> existiert, haben wir soeben einen Link zum Zurücksetzen des Passworts dorthin gesendet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Link zurück zum Login -->
|
||||||
|
<div class="footer-link">
|
||||||
|
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
||||||
|
</div>
|
||||||
@@ -1,11 +1,39 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-forgot-password',
|
selector: 'app-forgot-password',
|
||||||
imports: [],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
templateUrl: './forgot-password.component.html',
|
templateUrl: './forgot-password.component.html',
|
||||||
styleUrl: './forgot-password.component.css'
|
styleUrl: './forgot-password.component.css'
|
||||||
})
|
})
|
||||||
export class ForgotPasswordComponent {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1 +1,60 @@
|
|||||||
<p>login works!</p>
|
<div class="component-header">
|
||||||
|
<h2 class="auth-title">Anmelden</h2>
|
||||||
|
<p class="auth-subtitle">Starten Sie, indem Sie Ihre Daten eingeben.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- [formGroup] bindet das Formular an unsere loginForm-Variable im TypeScript -->
|
||||||
|
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
|
|
||||||
|
<!-- E-Mail-Feld -->
|
||||||
|
<div class="form-field">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
class="form-input"
|
||||||
|
id="email"
|
||||||
|
placeholder=" "
|
||||||
|
formControlName="email"> <!-- Verbindet dieses Input-Feld mit 'email' im FormGroup -->
|
||||||
|
<label for="email" class="form-label">E-Mail-Adresse</label>
|
||||||
|
</div>
|
||||||
|
<!-- Hier könnten Validierungs-Fehlermeldungen angezeigt werden -->
|
||||||
|
|
||||||
|
<!-- Passwort-Feld -->
|
||||||
|
<div class="form-field">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-input"
|
||||||
|
id="password"
|
||||||
|
placeholder=" "
|
||||||
|
formControlName="password"> <!-- Verbindet dieses Input-Feld mit 'password' im FormGroup -->
|
||||||
|
<label for="password" class="form-label">Passwort</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- "Passwort vergessen?" Link -->
|
||||||
|
<div class="form-actions">
|
||||||
|
<!-- routerLink navigiert zur "forgot-password"-Route, die Sie definieren müssen -->
|
||||||
|
<a routerLink="/auth/forgot-password" class="link">Passwort vergessen?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Absende-Button -->
|
||||||
|
<!-- [disabled] wird dynamisch an die Gültigkeit des Formulars gebunden -->
|
||||||
|
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="loginForm.invalid">
|
||||||
|
Anmelden
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Visueller Trenner -->
|
||||||
|
<div class="divider-or">
|
||||||
|
<span>oder</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Social Login Button (Beispiel) -->
|
||||||
|
<button type="button" class="btn btn-secondary btn-full-width btn-icon-left">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/><path d="M1 1h22v22H1z" fill="none"/></svg>
|
||||||
|
<span>Mit Google anmelden</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Link zur Registrierung -->
|
||||||
|
<div class="footer-link">
|
||||||
|
Noch kein Konto? <a routerLink="/auth/register" class="link">Jetzt registrieren</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
@@ -1,11 +1,36 @@
|
|||||||
import { Component } from '@angular/core';
|
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({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
imports: [],
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
RouterLink,
|
||||||
|
ReactiveFormsModule // <-- HIER IST DIE KORREKTUR. Jetzt kennt die Komponente [formGroup].
|
||||||
|
],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.css'
|
styleUrl: './login.component.css',
|
||||||
})
|
})
|
||||||
export class LoginComponent {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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. */
|
||||||
@@ -1 +1,39 @@
|
|||||||
<p>register works!</p>
|
<div class="component-header">
|
||||||
|
<h2 class="auth-title">Konto erstellen</h2>
|
||||||
|
<p class="auth-subtitle">Starten Sie, indem Sie Ihre Daten eingeben.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="text" class="form-input" id="fullName" placeholder=" " formControlName="fullName">
|
||||||
|
<label for="fullName" class="form-label">Vollständiger Name</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="email" class="form-input" id="email" placeholder=" " formControlName="email">
|
||||||
|
<label for="email" class="form-label">E-Mail-Adresse</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="password" class="form-input" id="password" placeholder=" " formControlName="password">
|
||||||
|
<label for="password" class="form-label">Passwort (min. 8 Zeichen)</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="password" class="form-input" id="confirmPassword" placeholder=" " formControlName="confirmPassword">
|
||||||
|
<label for="confirmPassword" class="form-label">Passwort bestätigen</label>
|
||||||
|
<!-- Fehlermeldung für nicht übereinstimmende Passwörter -->
|
||||||
|
<div *ngIf="registerForm.errors?.['passwordMismatch'] && registerForm.get('confirmPassword')?.touched" class="error-text">
|
||||||
|
Die Passwörter stimmen nicht überein.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="registerForm.invalid">
|
||||||
|
Registrieren
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="footer-link">
|
||||||
|
Bereits ein Konto? <a routerLink="/auth/login" class="link">Jetzt anmelden</a>
|
||||||
|
</div>
|
||||||
@@ -1,11 +1,37 @@
|
|||||||
import { Component } from '@angular/core';
|
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({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
imports: [],
|
imports: [CommonModule, ReactiveFormsModule, RouterLink],
|
||||||
templateUrl: './register.component.html',
|
templateUrl: './register.component.html',
|
||||||
styleUrl: './register.component.css'
|
styleUrl: './register.component.css'
|
||||||
})
|
})
|
||||||
export class RegisterComponent {
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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%; }
|
||||||
@@ -1 +1,21 @@
|
|||||||
<p>reset-password works!</p>
|
<div class="component-header">
|
||||||
|
<h2 class="auth-title">Neues Passwort festlegen</h2>
|
||||||
|
<p class="auth-subtitle">Bitte geben Sie Ihr neues, sicheres Passwort ein.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="password" class="form-input" id="password" placeholder=" " formControlName="password">
|
||||||
|
<label for="password" class="form-label">Neues Passwort</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-field">
|
||||||
|
<input type="password" class="form-input" id="confirmPassword" placeholder=" " formControlName="confirmPassword">
|
||||||
|
<label for="confirmPassword" class="form-label">Neues Passwort bestätigen</label>
|
||||||
|
<div *ngIf="resetPasswordForm.errors?.['passwordMismatch'] && resetPasswordForm.get('confirmPassword')?.touched" class="error-text">
|
||||||
|
Die Passwörter stimmen nicht überein.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="resetPasswordForm.invalid">
|
||||||
|
Passwort speichern
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
@@ -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({
|
@Component({
|
||||||
selector: 'app-reset-password',
|
selector: 'app-reset-password',
|
||||||
imports: [],
|
standalone: true,
|
||||||
|
imports: [CommonModule, ReactiveFormsModule],
|
||||||
templateUrl: './reset-password.component.html',
|
templateUrl: './reset-password.component.html',
|
||||||
styleUrl: './reset-password.component.css'
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1 +1,17 @@
|
|||||||
<p>verify-email works!</p>
|
<div class="component-header">
|
||||||
|
<h2 class="auth-title">Bestätigen Sie Ihre E-Mail</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="verify-content">
|
||||||
|
<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
||||||
|
<p class="info-text">
|
||||||
|
Ein Bestätigungslink wurde an Ihre E-Mail-Adresse gesendet. Bitte klicken Sie darauf, um Ihr Konto zu aktivieren.
|
||||||
|
</p>
|
||||||
|
<p class="info-text small">
|
||||||
|
Keine E-Mail erhalten? <a href="#" class="link">Erneut senden</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer-link">
|
||||||
|
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
||||||
|
</div>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-verify-email',
|
selector: 'app-verify-email',
|
||||||
imports: [],
|
standalone: true,
|
||||||
|
imports: [RouterLink],
|
||||||
templateUrl: './verify-email.component.html',
|
templateUrl: './verify-email.component.html',
|
||||||
styleUrl: './verify-email.component.css'
|
styleUrl: './verify-email.component.css'
|
||||||
})
|
})
|
||||||
export class VerifyEmailComponent {
|
export class VerifyEmailComponent {
|
||||||
|
// Diese Komponente ist oft rein statisch und benötigt keine komplexe Logik
|
||||||
}
|
}
|
||||||
@@ -349,6 +349,7 @@ body.dark-theme .btn-icon-danger:hover {
|
|||||||
padding: 0 0.25rem;
|
padding: 0 0.25rem;
|
||||||
transition: all 0.2s ease-out;
|
transition: all 0.2s ease-out;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
}
|
}
|
||||||
.form-input:focus ~ .form-label,
|
.form-input:focus ~ .form-label,
|
||||||
.form-input:not(:placeholder-shown) ~ .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);
|
background-color: var(--color-body-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
Reference in New Issue
Block a user