Fertig- next ist dashboard
This commit is contained in:
@@ -9,14 +9,9 @@ export const routes: Routes = [
|
|||||||
// wird sofort und ohne Umwege zur Login-Seite weitergeleitet.
|
// wird sofort und ohne Umwege zur Login-Seite weitergeleitet.
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'auth', // Leitet zur /auth-Route weiter
|
redirectTo: 'auth',
|
||||||
pathMatch: 'full', // Wichtig: Gilt nur für den exakt leeren Pfad
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
|
|
||||||
// Regel 2: Authentifizierungs-Feature
|
|
||||||
// Alle URLs, die mit "auth/" beginnen (z.B. "/auth/login", "/auth/register"),
|
|
||||||
// werden von dieser Regel abgefangen und an die auth.routes.ts zur
|
|
||||||
// weiteren Verarbeitung übergeben.
|
|
||||||
{
|
{
|
||||||
path: 'auth',
|
path: 'auth',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
@@ -27,14 +22,11 @@ export const routes: Routes = [
|
|||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./features/demo/demo.routes').then((r) => r.DEMO_ROUTES),
|
import('./features/demo/demo.routes').then((r) => r.DEMO_ROUTES),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: 'access-denied',
|
path: 'access-denied',
|
||||||
component: AccessDeniedComponent,
|
component: AccessDeniedComponent,
|
||||||
title: 'Zugriff verweigert',
|
title: 'Zugriff verweigert',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
component: NotFoundComponent,
|
component: NotFoundComponent,
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
<!-- <div class="not-found-container">
|
<div class="not-found-container">
|
||||||
<div class="not-found-box">
|
<div class="not-found-box">
|
||||||
|
|
||||||
<div class="status-code">404</div>
|
<div class="status-code">404</div>
|
||||||
<h1 class="title">Seite nicht gefunden</h1>
|
<h1 class="title">Seite nicht gefunden</h1>
|
||||||
<p class="subtitle">
|
<p class="subtitle">
|
||||||
Die von Ihnen angeforderte Seite konnte leider nicht gefunden werden.
|
Die von Ihnen angeforderte Seite konnte leider nicht gefunden werden.
|
||||||
Möglicherweise wurde sie verschoben, gelöscht oder die URL ist falsch.
|
Möglicherweise wurde sie verschoben, gelöscht oder die URL ist falsch.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-stroked" (click)="goBack()">
|
<div class="button-group">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
|
<app-button buttonType="primary" (click)="goBack()">Zurück</app-button>
|
||||||
<span>Zurück</span>
|
<app-button buttonType="secondary" routerLink="/"
|
||||||
</button>
|
>Zur Startseite</app-button
|
||||||
<button class="btn btn-primary" routerLink="/">
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
|
</div>
|
||||||
<span>Zur Startseite</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div> -->
|
</div>
|
||||||
|
|
||||||
<p>404</p>
|
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ import { Component } from '@angular/core';
|
|||||||
import { CommonModule, Location } from '@angular/common';
|
import { CommonModule, Location } from '@angular/common';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
|
||||||
|
import { ButtonComponent } from '../../../shared/components/ui/button/button.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-not-found',
|
selector: 'app-not-found',
|
||||||
imports: [
|
imports: [CommonModule, ButtonComponent,RouterLink],
|
||||||
CommonModule
|
|
||||||
|
|
||||||
],
|
|
||||||
templateUrl: './not-found.component.html',
|
templateUrl: './not-found.component.html',
|
||||||
styleUrl: './not-found.component.css'
|
styleUrl: './not-found.component.css',
|
||||||
})
|
})
|
||||||
export class NotFoundComponent {
|
export class NotFoundComponent {
|
||||||
|
constructor(private location: Location) {}
|
||||||
constructor(private location: Location) { }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigiert den Benutzer eine Seite in der Browser-Historie zurück.
|
* Navigiert den Benutzer eine Seite in der Browser-Historie zurück.
|
||||||
@@ -21,4 +19,4 @@ export class NotFoundComponent {
|
|||||||
goBack(): void {
|
goBack(): void {
|
||||||
this.location.back();
|
this.location.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
/* src\app\features\auth\_auth-common.css */
|
|
||||||
/* =================================================================================
|
|
||||||
* 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; }
|
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ import { LoginComponent } from './components/login/login.component';
|
|||||||
import { RegisterComponent } from './components/register/register.component';
|
import { RegisterComponent } from './components/register/register.component';
|
||||||
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
|
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
|
||||||
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
|
||||||
|
import { VerifyEmailComponent } from './components/verify-email/verify-email.component';
|
||||||
|
import { NotFoundComponent } from '../../core/components/not-found/not-found.component';
|
||||||
|
|
||||||
export const AUTH_ROUTES: Routes = [
|
export const AUTH_ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
@@ -26,6 +28,11 @@ export const AUTH_ROUTES: Routes = [
|
|||||||
component: ResetPasswordComponent,
|
component: ResetPasswordComponent,
|
||||||
title: 'Neues Passwort',
|
title: 'Neues Passwort',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'verify-email/:token',
|
||||||
|
component: VerifyEmailComponent,
|
||||||
|
title: 'Email bestätigen',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,39 +1,56 @@
|
|||||||
/* src\app\features\auth\components\auth-layout\auth-layout.component.css */
|
/* =================================================================================
|
||||||
/* Stile NUR für den äußeren Rahmen aller Auth-Seiten */
|
FINALES & ZENTRALISIERTES STYLING FÜR DAS AUTH-LAYOUT
|
||||||
|
================================================================================== */
|
||||||
|
|
||||||
|
:host ::ng-deep .form-content-wrapper {
|
||||||
|
flex-grow: 1; /* Sorgt dafür, dass der Inhalt den Platz füllt */
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep .verify-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .verify-content app-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
color: var(--color-primary);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .verify-content .info-text.small {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. Basis-Layout für den Container und die Karte */
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: var(--color-body-bg);
|
background-color: var(--color-body-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-container {
|
.auth-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-card {
|
.auth-card {
|
||||||
padding: 2rem;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
animation: fade-in 0.5s ease-out forwards;
|
animation: fade-in 0.5s ease-out forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-header {
|
.auth-header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
.auth-header app-icon {
|
||||||
.auth-logo {
|
font-size: 48px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
background: var(--color-primary-gradient);
|
background: var(--color-primary-gradient);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fade-in {
|
@keyframes fade-in {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -44,3 +61,94 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 2. Flexbox-Layout für den Karteninhalt (DER ENTSCHEIDENDE TEIL) */
|
||||||
|
/* Wir verwenden ::ng-deep, damit diese Layout-Regeln auf die projizierten Inhalte wirken. */
|
||||||
|
:host ::ng-deep .auth-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Stile für die gemeinsamen Elemente innerhalb der Auth-Seiten */
|
||||||
|
:host ::ng-deep .component-header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .auth-title {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .auth-subtitle {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Der <form>-Tag oder ein anderer Inhalts-Wrapper wird anpassungsfähig */
|
||||||
|
:host ::ng-deep form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.25rem;
|
||||||
|
flex-grow: 1; /* WICHTIG: Nimmt den verfügbaren Platz ein */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spezifische Formular-Elemente */
|
||||||
|
:host ::ng-deep .form-actions {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: -0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .error-text {
|
||||||
|
color: var(--color-danger);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .info-text {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links & Footer */
|
||||||
|
:host ::ng-deep .link {
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: color var(--transition-speed);
|
||||||
|
}
|
||||||
|
:host ::ng-deep .link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--color-primary-dark);
|
||||||
|
}
|
||||||
|
:host ::ng-deep .footer-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: auto; /* WICHTIG: Schiebt den Footer immer nach ganz unten */
|
||||||
|
padding-top: 1.5rem; /* Sorgt für Abstand zum Element darüber */
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visueller Trenner */
|
||||||
|
:host ::ng-deep .divider-or {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .divider-or::before,
|
||||||
|
:host ::ng-deep .divider-or::after {
|
||||||
|
content: "";
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
:host ::ng-deep .divider-or:not(:empty)::before {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
:host ::ng-deep .divider-or:not(:empty)::after {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +1,11 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="auth-container">
|
<div class="auth-container">
|
||||||
<div class="auth-card card">
|
<!-- Wir verwenden jetzt eine app-card für den konsistenten Look -->
|
||||||
<div class="auth-header">
|
<app-card class="auth-card">
|
||||||
<svg
|
<!-- <div class="auth-header">
|
||||||
class="auth-logo"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</div> -->
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
>
|
|
||||||
<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path>
|
|
||||||
<polyline points="10 17 15 12 10 7"></polyline>
|
|
||||||
<line x1="15" y1="12" x2="3" y2="12"></line>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Der RouterOutlet rendert die spezifischen Inhalte (Login, Register, etc.) -->
|
|
||||||
<div class="auth-content">
|
<div class="auth-content">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</app-card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import { Component } from '@angular/core';
|
|||||||
import { RouterOutlet } from '@angular/router';
|
import { RouterOutlet } from '@angular/router';
|
||||||
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
import { CardComponent } from '../../../../shared/components/ui/card/card.component';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-auth-layout',
|
selector: 'app-auth-layout',
|
||||||
imports: [RouterOutlet, CardComponent],
|
imports: [RouterOutlet, CardComponent],
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@@ -2,28 +2,39 @@
|
|||||||
<h2 class="auth-title">Passwort vergessen?</h2>
|
<h2 class="auth-title">Passwort vergessen?</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!emailSent">
|
<!-- Container für den Inhalt, damit der Footer nach unten rutscht -->
|
||||||
<p class="info-text">
|
<div class="form-content-wrapper">
|
||||||
Geben Sie Ihre E-Mail-Adresse ein. Wir senden Ihnen einen Link, mit dem Sie Ihr Passwort zurücksetzen können.
|
<!-- Fall 1: Formular anzeigen -->
|
||||||
</p>
|
<div *ngIf="!emailSent">
|
||||||
<form [formGroup]="forgotPasswordForm" (ngSubmit)="onSubmit()" novalidate>
|
<p class="info-text">
|
||||||
<div class="form-field">
|
Geben Sie Ihre E-Mail ein, wir senden Ihnen einen Link zum Zurücksetzen.
|
||||||
<input type="email" class="form-input" id="email" placeholder=" " formControlName="email">
|
</p>
|
||||||
<label for="email" class="form-label">E-Mail-Adresse</label>
|
<form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate>
|
||||||
</div>
|
<app-form-field
|
||||||
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="forgotPasswordForm.invalid">
|
label="E-Mail-Adresse"
|
||||||
Link anfordern
|
type="email"
|
||||||
</button>
|
formControlName="email"
|
||||||
</form>
|
></app-form-field>
|
||||||
</div>
|
<app-button
|
||||||
|
submitType="submit"
|
||||||
|
buttonType="primary"
|
||||||
|
[fullWidth]="true"
|
||||||
|
[disabled]="form.invalid"
|
||||||
|
>Link anfordern</app-button
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="emailSent" class="success-message">
|
<!-- Fall 2: Erfolgsmeldung anzeigen -->
|
||||||
<h3 class="success-title">Prüfen Sie Ihr Postfach</h3>
|
<div *ngIf="emailSent" class="success-message">
|
||||||
<p class="info-text">
|
<h2 class="auth-title">Prüfen Sie Ihr Postfach</h2>
|
||||||
Wenn ein Konto mit <strong>{{ forgotPasswordForm.value.email }}</strong> existiert, wurde ein Link versendet.
|
<p class="info-text">
|
||||||
</p>
|
Wenn ein Konto existiert, wurde ein Link an
|
||||||
|
<strong>{{ form.value.email }}</strong> gesendet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-link">
|
<div class="footer-link">
|
||||||
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,38 +2,39 @@ import { Component } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
|
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||||
|
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-forgot-password',
|
selector: 'app-forgot-password',
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
RouterLink
|
RouterLink,ButtonComponent,FormFieldComponent
|
||||||
],
|
],
|
||||||
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;
|
form: FormGroup;
|
||||||
// Optional: Eine Eigenschaft, um eine Erfolgsmeldung anzuzeigen
|
|
||||||
emailSent = false;
|
emailSent = false;
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
constructor(private fb: FormBuilder) {
|
||||||
this.forgotPasswordForm = this.fb.group({
|
this.form = this.fb.group({
|
||||||
email: ['', [Validators.required, Validators.email]]
|
email: ['', [Validators.required, Validators.email]]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
if (this.forgotPasswordForm.valid) {
|
if (this.form.valid) {
|
||||||
// Hier würde die Logik zum Senden der E-Mail an das Backend stehen
|
// 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);
|
console.log('Anforderung zum Zurücksetzen des Passworts für:', this.form.value.email);
|
||||||
// z.B. this.authService.sendPasswordResetEmail(this.forgotPasswordForm.value.email);
|
// z.B. this.authService.sendPasswordResetEmail(this.forgotPasswordForm.value.email);
|
||||||
|
|
||||||
// Setze den Zustand, um die Erfolgsmeldung anzuzeigen
|
// Setze den Zustand, um die Erfolgsmeldung anzuzeigen
|
||||||
this.emailSent = true;
|
this.emailSent = true;
|
||||||
} else {
|
} else {
|
||||||
this.forgotPasswordForm.markAllAsTouched();
|
this.form.markAllAsTouched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +1,43 @@
|
|||||||
<!-- Seitenspezifischer Titel und Untertitel -->
|
|
||||||
<div class="component-header">
|
<div class="component-header">
|
||||||
<h2 class="auth-title">Anmelden</h2>
|
<h2 class="auth-title">Anmelden</h2>
|
||||||
<p class="auth-subtitle">Bitte geben Sie Ihre Daten ein, um fortzufahren.</p>
|
<p class="auth-subtitle">Bitte geben Sie Ihre Daten ein, um fortzufahren.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
|
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="email" class="form-input" id="email" placeholder=" " formControlName="email">
|
label="E-Mail-Adresse"
|
||||||
<label for="email" class="form-label">E-Mail-Adresse</label>
|
type="email"
|
||||||
</div>
|
formControlName="email">
|
||||||
|
</app-form-field>
|
||||||
|
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="password" class="form-input" id="password" placeholder=" " formControlName="password">
|
label="Passwort"
|
||||||
<label for="password" class="form-label">Passwort</label>
|
type="password"
|
||||||
</div>
|
formControlName="password">
|
||||||
|
</app-form-field>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<a routerLink="/auth/forgot-password" class="link">Passwort vergessen?</a>
|
<a routerLink="/auth/forgot-password" class="link">Passwort vergessen?</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="loginForm.invalid">
|
<app-button
|
||||||
|
submitType="submit"
|
||||||
|
buttonType="primary"
|
||||||
|
[fullWidth]="true"
|
||||||
|
[disabled]="loginForm.invalid">
|
||||||
Anmelden
|
Anmelden
|
||||||
</button>
|
</app-button>
|
||||||
|
|
||||||
<div class="divider-or"><span>oder</span></div>
|
<div class="divider-or"><span>oder</span></div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-secondary btn-full-width btn-icon-left">
|
<app-button
|
||||||
<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>
|
buttonType="secondary"
|
||||||
<span>Mit Google anmelden</span>
|
[fullWidth]="true"
|
||||||
</button>
|
iconName="placeholder">
|
||||||
|
Mit Google anmelden
|
||||||
|
</app-button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div class="footer-link">
|
<div class="footer-link">
|
||||||
Noch kein Konto? <a routerLink="/auth/register" class="link">Jetzt registrieren</a>
|
Noch kein Konto? <a routerLink="/auth/register" class="link">Jetzt registrieren</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
@@ -28,6 +28,8 @@ import { FormFieldComponent } from '../../../../shared/components/form/form-fiel
|
|||||||
export class LoginComponent {
|
export class LoginComponent {
|
||||||
loginForm: FormGroup;
|
loginForm: FormGroup;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor(private fb: FormBuilder) {
|
constructor(private fb: FormBuilder) {
|
||||||
this.loginForm = this.fb.group({
|
this.loginForm = this.fb.group({
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
/* 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. */
|
|
||||||
@@ -4,34 +4,44 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" novalidate>
|
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="text" class="form-input" id="fullName" placeholder=" " formControlName="fullName">
|
label="Vollständiger Name"
|
||||||
<label for="fullName" class="form-label">Vollständiger Name</label>
|
type="text"
|
||||||
</div>
|
formControlName="fullName"
|
||||||
|
></app-form-field>
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="email" class="form-input" id="email" placeholder=" " formControlName="email">
|
label="E-Mail-Adresse"
|
||||||
<label for="email" class="form-label">E-Mail-Adresse</label>
|
type="email"
|
||||||
</div>
|
formControlName="email"
|
||||||
|
></app-form-field>
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="password" class="form-input" id="password" placeholder=" " formControlName="password">
|
label="Passwort (min. 8 Zeichen)"
|
||||||
<label for="password" class="form-label">Passwort (min. 8 Zeichen)</label>
|
type="password"
|
||||||
|
formControlName="password"
|
||||||
|
></app-form-field>
|
||||||
|
<app-form-field
|
||||||
|
label="Passwort bestätigen"
|
||||||
|
type="password"
|
||||||
|
formControlName="confirmPassword"
|
||||||
|
></app-form-field>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="registerForm.errors?.['passwordMismatch'] && registerForm.get('confirmPassword')?.touched"
|
||||||
|
class="error-text"
|
||||||
|
>
|
||||||
|
Die Passwörter stimmen nicht überein.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-field">
|
<app-button
|
||||||
<input type="password" class="form-input" id="confirmPassword" placeholder=" " formControlName="confirmPassword">
|
submitType="submit"
|
||||||
<label for="confirmPassword" class="form-label">Passwort bestätigen</label>
|
buttonType="primary"
|
||||||
<div *ngIf="registerForm.errors?.['passwordMismatch'] && registerForm.get('confirmPassword')?.touched" class="error-text">
|
[fullWidth]="true"
|
||||||
Die Passwörter stimmen nicht überein.
|
[disabled]="registerForm.invalid"
|
||||||
</div>
|
>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="registerForm.invalid">
|
|
||||||
Registrieren
|
Registrieren
|
||||||
</button>
|
</app-button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="footer-link">
|
<div class="footer-link">
|
||||||
Bereits ein Konto? <a routerLink="/auth/login" class="link">Jetzt anmelden</a>
|
Bereits ein Konto? <a routerLink="/auth/login" class="link">Jetzt anmelden</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angula
|
|||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
// Optional: Ein Custom Validator für den Passwort-Vergleich
|
// Optional: Ein Custom Validator für den Passwort-Vergleich
|
||||||
import { passwordMatchValidator } from '../../../../shared/validators/password-match.validator';
|
import { passwordMatchValidator } from '../../../../shared/validators/password-match.validator';
|
||||||
|
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||||
|
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
imports: [CommonModule, ReactiveFormsModule, RouterLink],
|
imports: [CommonModule, ReactiveFormsModule, RouterLink,ButtonComponent,FormFieldComponent],
|
||||||
templateUrl: './register.component.html',
|
templateUrl: './register.component.html',
|
||||||
styleUrl: './register.component.css'
|
styleUrl: './register.component.css'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
/* src\app\features\auth\components\reset-password\reset-password.component.css */
|
|
||||||
@import '../../_auth-common.css';
|
|
||||||
|
|
||||||
/* Stile NUR für die Passwort-zurücksetzen-Seite */
|
|
||||||
:host { display: block; width: 100%; }
|
|
||||||
@@ -4,18 +4,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()" novalidate>
|
<form [formGroup]="resetPasswordForm" (ngSubmit)="onSubmit()" novalidate>
|
||||||
<div class="form-field">
|
<app-form-field
|
||||||
<input type="password" class="form-input" id="password" placeholder=" " formControlName="password">
|
label="Neues Passwort"
|
||||||
<label for="password" class="form-label">Neues Passwort</label>
|
type="password"
|
||||||
|
formControlName="password"
|
||||||
|
></app-form-field>
|
||||||
|
<app-form-field
|
||||||
|
label="Passwort bestätigen"
|
||||||
|
type="password"
|
||||||
|
formControlName="confirmPassword"
|
||||||
|
></app-form-field>
|
||||||
|
|
||||||
|
<div
|
||||||
|
*ngIf="resetPasswordForm.errors?.['passwordMismatch'] && resetPasswordForm.get('confirmPassword')?.touched"
|
||||||
|
class="error-text"
|
||||||
|
>
|
||||||
|
Die Passwörter stimmen nicht überein.
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" class="form-input" id="confirmPassword" placeholder=" " formControlName="confirmPassword">
|
<app-button
|
||||||
<label for="confirmPassword" class="form-label">Neues Passwort bestätigen</label>
|
submitType="submit"
|
||||||
<div *ngIf="resetPasswordForm.errors?.['passwordMismatch'] && resetPasswordForm.get('confirmPassword')?.touched" class="error-text">
|
buttonType="primary"
|
||||||
Die Passwörter stimmen nicht überein.
|
[fullWidth]="true"
|
||||||
</div>
|
[disabled]="resetPasswordForm.invalid"
|
||||||
</div>
|
>
|
||||||
<button type="submit" class="btn btn-primary btn-full-width" [disabled]="resetPasswordForm.invalid">
|
|
||||||
Passwort speichern
|
Passwort speichern
|
||||||
</button>
|
</app-button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,43 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
|
import {
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormBuilder,
|
||||||
|
Validators,
|
||||||
|
FormGroup,
|
||||||
|
} from '@angular/forms';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { passwordMatchValidator } from '../../../../shared/validators/password-match.validator';
|
import { passwordMatchValidator } from '../../../../shared/validators/password-match.validator';
|
||||||
|
import { ButtonComponent } from '../../../../shared/components/ui/button/button.component';
|
||||||
|
import { FormFieldComponent } from '../../../../shared/components/form/form-field/form-field.component';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-reset-password',
|
selector: 'app-reset-password',
|
||||||
|
|
||||||
imports: [CommonModule, ReactiveFormsModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ButtonComponent,
|
||||||
|
FormFieldComponent,
|
||||||
|
],
|
||||||
templateUrl: './reset-password.component.html',
|
templateUrl: './reset-password.component.html',
|
||||||
styleUrl: './reset-password.component.css'
|
styleUrl: './reset-password.component.css',
|
||||||
})
|
})
|
||||||
export class ResetPasswordComponent implements OnInit {
|
export class ResetPasswordComponent implements OnInit {
|
||||||
resetPasswordForm: FormGroup;
|
resetPasswordForm: FormGroup;
|
||||||
token: string | null = null;
|
token: string | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
this.resetPasswordForm = this.fb.group({
|
this.resetPasswordForm = this.fb.group(
|
||||||
password: ['', [Validators.required, Validators.minLength(8)]],
|
{
|
||||||
confirmPassword: ['', [Validators.required]]
|
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||||
}, { validators: passwordMatchValidator });
|
confirmPassword: ['', [Validators.required]],
|
||||||
|
},
|
||||||
|
{ validators: passwordMatchValidator }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -45,4 +59,4 @@ export class ResetPasswordComponent implements OnInit {
|
|||||||
this.resetPasswordForm.markAllAsTouched();
|
this.resetPasswordForm.markAllAsTouched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
/* 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,12 +1,12 @@
|
|||||||
<div class="component-header">
|
<div class="component-header">
|
||||||
<h2 class="auth-title">Bestätigen Sie Ihre E-Mail</h2>
|
<h2 class="auth-title">Bestätigen Sie Ihre E-Mail</h2>
|
||||||
|
<p class="auth-subtitle">
|
||||||
|
Ein Bestätigungslink wurde an Ihre E-Mail gesendet. Bitte aktivieren Sie Ihr
|
||||||
|
Konto.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="verify-content">
|
<div class="form-content-wrapper 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">
|
<p class="info-text small">
|
||||||
Keine E-Mail erhalten? <a href="#" class="link">Erneut senden</a>.
|
Keine E-Mail erhalten? <a href="#" class="link">Erneut senden</a>.
|
||||||
</p>
|
</p>
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
|
|
||||||
<div class="footer-link">
|
<div class="footer-link">
|
||||||
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
<a routerLink="/auth/login" class="link">Zurück zum Login</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,26 +26,43 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-col-span-2">
|
<div class="grid-col-span-2">
|
||||||
<app-form-group
|
<form [formGroup]="demoForm">
|
||||||
title="Form Components 1"
|
<app-form-group
|
||||||
description="Diese Informationen sind öffentlich sichtbar."
|
title="Form Components 1"
|
||||||
>
|
description="Diese Informationen sind öffentlich sichtbar."
|
||||||
<app-form-select
|
|
||||||
label="Land"
|
|
||||||
[options]="countryOptions"
|
|
||||||
[(ngModel)]="selectedCountry"
|
|
||||||
>
|
>
|
||||||
</app-form-select>
|
<app-form-select
|
||||||
|
label="Land"
|
||||||
|
[options]="countryOptions"
|
||||||
|
[(ngModel)]="selectedCountry"
|
||||||
|
[ngModelOptions]="{standalone: true}"> <!-- Wichtig für die Mischung von ngModel und formGroup -->
|
||||||
|
</app-form-select>
|
||||||
|
|
||||||
<app-form-field label="Benutzername" type="text" [(value)]="benutzername">
|
<!-- Diese Felder verwenden jetzt formControlName -->
|
||||||
</app-form-field>
|
<app-form-field
|
||||||
|
label="Benutzername"
|
||||||
|
type="text"
|
||||||
|
formControlName="username">
|
||||||
|
</app-form-field>
|
||||||
|
<app-form-field
|
||||||
|
label="E-Mail-Adresse"
|
||||||
|
type="email"
|
||||||
|
formControlName="email">
|
||||||
|
</app-form-field>
|
||||||
|
<app-form-field
|
||||||
|
label="Passwort"
|
||||||
|
type="password"
|
||||||
|
formControlName="password">
|
||||||
|
</app-form-field>
|
||||||
|
|
||||||
<app-form-field label="E-Mail-Adresse" type="email" [(value)]="email">
|
<app-form-textarea
|
||||||
</app-form-field>
|
label="Biografie"
|
||||||
|
[rows]="5"
|
||||||
<app-form-textarea label="Biografie" [rows]="5" [(ngModel)]="biografie">
|
[(ngModel)]="biografie"
|
||||||
</app-form-textarea>
|
[ngModelOptions]="{standalone: true}">
|
||||||
|
</app-form-textarea>
|
||||||
</app-form-group>
|
</app-form-group>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid-col-span-2">
|
<div class="grid-col-span-2">
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ import {
|
|||||||
SelectOption,
|
SelectOption,
|
||||||
} from '../../../../shared/components/form/form-select/form-select.component';
|
} from '../../../../shared/components/form/form-select/form-select.component';
|
||||||
|
|
||||||
import { FormsModule } from '@angular/forms';
|
import {
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
FormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms';
|
||||||
|
|
||||||
import { FormTextareaComponent } from '../../../../shared/components/form/form-textarea/form-textarea.component';
|
import { FormTextareaComponent } from '../../../../shared/components/form/form-textarea/form-textarea.component';
|
||||||
|
|
||||||
@@ -40,6 +45,8 @@ import { ButtonComponent } from '../../../../shared/components/ui/button/button.
|
|||||||
|
|
||||||
import { ChipComponent } from '../../../../shared/components/ui/chip/chip.component';
|
import { ChipComponent } from '../../../../shared/components/ui/chip/chip.component';
|
||||||
|
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
// Wir definieren ein Interface für unsere KPI-Daten für Typsicherheit
|
// Wir definieren ein Interface für unsere KPI-Daten für Typsicherheit
|
||||||
interface Kpi {
|
interface Kpi {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -65,11 +72,16 @@ interface Kpi {
|
|||||||
ExpansionPanelComponent,
|
ExpansionPanelComponent,
|
||||||
PageHeaderComponent,
|
PageHeaderComponent,
|
||||||
ButtonComponent,
|
ButtonComponent,
|
||||||
ChipComponent
|
ChipComponent,
|
||||||
|
ReactiveFormsModule
|
||||||
],
|
],
|
||||||
templateUrl: './demo2.component.html',
|
templateUrl: './demo2.component.html',
|
||||||
})
|
})
|
||||||
export class Demo2Component {
|
export class Demo2Component {
|
||||||
|
|
||||||
|
demoForm: FormGroup;
|
||||||
|
|
||||||
|
|
||||||
kpiData: Kpi[] = [
|
kpiData: Kpi[] = [
|
||||||
{
|
{
|
||||||
value: '€ 14.750',
|
value: '€ 14.750',
|
||||||
@@ -199,8 +211,6 @@ export class Demo2Component {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
benutzername: string = '';
|
|
||||||
email: string = '';
|
|
||||||
aktuellesPasswort: any = '';
|
aktuellesPasswort: any = '';
|
||||||
neuesPasswort: any = '';
|
neuesPasswort: any = '';
|
||||||
|
|
||||||
@@ -221,11 +231,20 @@ export class Demo2Component {
|
|||||||
private readonly darkModeKey = 'app-dark-mode-setting';
|
private readonly darkModeKey = 'app-dark-mode-setting';
|
||||||
darkModeAktiv: boolean = false;
|
darkModeAktiv: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private renderer: Renderer2,
|
private renderer: Renderer2,
|
||||||
@Inject(DOCUMENT) private document: Document,
|
@Inject(DOCUMENT) private document: Document,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {}
|
|
||||||
|
private fb: FormBuilder
|
||||||
|
) {
|
||||||
|
this.demoForm = this.fb.group({
|
||||||
|
username: [''], // Entspricht dem [(value)]="benutzername"
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
password: ['', [Validators.required]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadThemeSetting();
|
this.loadThemeSetting();
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export const DEMO_ROUTES: Routes = [
|
|||||||
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
||||||
path: '1',
|
path: '1',
|
||||||
component: Demo1Component,
|
component: Demo1Component,
|
||||||
title: 'Demo',
|
title: 'Demo1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
// Diese Route passt auf '/demo/1' und lädt die Komponente genau einmal.
|
||||||
path: '2',
|
path: '2',
|
||||||
component: Demo2Component,
|
component: Demo2Component,
|
||||||
title: 'Demo',
|
title: 'Demo2',
|
||||||
},
|
},
|
||||||
// Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen,
|
// Hier könntest du weitere Routen wie '2', '3' etc. hinzufügen,
|
||||||
// die andere Komponenten laden.
|
// die andere Komponenten laden.
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<input
|
<input
|
||||||
[type]="type"
|
[type]="type"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
[id]="label"
|
[id]="controlId"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
[value]="value"
|
[disabled]="disabled"
|
||||||
(input)="onInput($event)"
|
[(ngModel)]="value"
|
||||||
[disabled]="disabled">
|
(ngModelChange)="onChange($event)"
|
||||||
<label [for]="label" class="form-label">{{ label }}</label>
|
(blur)="onTouched()">
|
||||||
|
|
||||||
|
<label [for]="controlId" class="form-label">{{ label }}</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,32 +1,57 @@
|
|||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, Input, forwardRef } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import {
|
||||||
|
FormsModule,
|
||||||
|
ControlValueAccessor,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
} from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-form-field',
|
selector: 'app-form-field',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule], // Kein FormsModule mehr nötig
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule, // Wichtig für [(ngModel)] im Template
|
||||||
|
],
|
||||||
templateUrl: './form-field.component.html',
|
templateUrl: './form-field.component.html',
|
||||||
styleUrl: './form-field.component.css',
|
styleUrl: './form-field.component.css',
|
||||||
// Kein 'providers'-Block für ControlValueAccessor mehr
|
providers: [
|
||||||
|
{
|
||||||
|
// Stellt diese Komponente als "Value Accessor" zur Verfügung
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => FormFieldComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class FormFieldComponent {
|
// Die Komponente implementiert die ControlValueAccessor-Schnittstelle
|
||||||
// EINGÄNGE: Werte, die von außen gesetzt werden
|
export class FormFieldComponent implements ControlValueAccessor {
|
||||||
@Input() label: string = '';
|
@Input() label: string = '';
|
||||||
@Input() type: 'text' | 'email' | 'password' = 'text';
|
@Input() type: 'text' | 'email' | 'password' = 'text';
|
||||||
@Input() value: string = ''; // Der aktuelle Wert des Feldes
|
|
||||||
@Input() disabled: boolean = false;
|
|
||||||
|
|
||||||
// AUSGANG: Ein Event, das ausgelöst wird, wenn sich der Wert ändert
|
controlId = `form-field-${Math.random().toString(36)}`;
|
||||||
// WICHTIG: Der Name muss `[InputName]Change` sein, also `valueChange`
|
|
||||||
@Output() valueChange = new EventEmitter<string>();
|
|
||||||
|
|
||||||
/**
|
// --- Eigenschaften für ControlValueAccessor ---
|
||||||
* Diese Methode wird bei jeder Tastatureingabe im Input-Feld aufgerufen.
|
value: string = '';
|
||||||
*/
|
onChange: (value: any) => void = () => {};
|
||||||
onInput(event: Event): void {
|
onTouched: () => void = () => {};
|
||||||
// 1. Hole den neuen Wert aus dem HTML-Input-Element
|
disabled = false;
|
||||||
const newValue = (event.target as HTMLInputElement).value;
|
|
||||||
// 2. Sende den neuen Wert über den EventEmitter nach außen
|
// --- Methoden, die von Angular Forms aufgerufen werden ---
|
||||||
this.valueChange.emit(newValue);
|
|
||||||
|
writeValue(value: any): void {
|
||||||
|
this.value = value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user