diff --git a/.gitignore b/.gitignore index add57be..fcde208 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,482 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +## +## Visual studio for Mac +## + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows shortcuts +*.lnk + +# Rider Idea bin/ obj/ /packages/ riderModule.iml -/_ReSharper.Caches/ \ No newline at end of file + +# Production settings (contains secrets) +src/API/appsettings.Production.json + +# JetBrains Rider IDE (user-specific) +.idea/ + +*.drawio +*.drawio.bkp +src/ClientApp/.angular/ +src/ClientApp/dist/ +src/ClientApp/coverage/ diff --git a/README.md b/README.md index b3d747a..6616816 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,233 @@ -# .NET + Angular Application Template +# DotNetAngular -This project is a full-stack web application Template combining **ASP.NET Core** for the backend and **Angular** for the frontend. +Full-Stack Webanwendung mit **ASP.NET Core (.NET 10)** und **Angular 21**, basierend auf **Clean Architecture** mit **PostgreSQL** als Datenbank. -## đŸ› ïž Technologies Used +--- -- **Backend:** ASP.NET Core (.NET 10) - -- **Frontend:** Angular - -- **API Documentation:** Swagger (Swashbuckle) +## Architektur -## 🔐 Authentication (Register & Login) +``` +src/ +├── API # ASP.NET Core Web API (Controller, Middleware, Swagger) +├── Application # Anwendungslogik (Services, DTOs, Validatoren) +├── Domain # EntitĂ€ten, Repository-Interfaces, Value Objects +└── Infrastructure # EF Core DbContext, Repositories, Migrations +tests/ +├── Application.UnitTest # xUnit-Unit-Tests +└── Application.FunctionalTest # xUnit-Funktionstests +``` -The template includes a preconfigured user registration and authentication flow, designed as a solid baseline for production-ready applications. +### Patterns +- **Clean Architecture** mit 4 Schichten (API → Application → Domain ← Infrastructure) +- **Repository & Unit of Work** Pattern +- **Result Pattern** fĂŒr explizite Erfolgs-/FehlerrĂŒckgaben (keine Exceptions fĂŒr Flow-Control) +- **FluentValidation** fĂŒr Request-Validierung +- **JWT-Authentifizierung** mit Refresh-Token +- **Role-Based Access Control** (SuperAdmin, Admin, User) -- **Register:** -Creation of new user accounts via a dedicated API endpoint with server-side validation and secure password hashing. +--- -- **Login:** -Token-based authentication using JWT (JSON Web Token). Upon successful authentication, a token is issued for authorized API access. +## Technologien -- **Authorization:** -Protected backend endpoints and frontend routes are secured using role- or policy-based access control. +| Bereich | Technologie | Version | +|---|---|---| +| **Backend** | ASP.NET Core | .NET 10 | +| **ORM** | Entity Framework Core | 10.0.7 | +| **Datenbank** | PostgreSQL (Npgsql) | 10.0.1 | +| **Auth** | JWT Bearer (System.IdentityModel.Tokens.Jwt) | 8.18.0 | +| **Validierung** | FluentValidation | 12.1.1 | +| **E-Mail** | MailKit / MimeKit | 4.16.0 | +| **API-Dokumentation** | Swagger (Swashbuckle) | 10.1.7 | +| **Frontend** | Angular (Standalone Components) | 21.2 | +| **UI-Framework** | Bootstrap + Bootstrap Icons | 5.3.8 / 1.13.1 | +| **JWT Frontend** | @auth0/angular-jwt | 5.2.0 | +| **Tests Backend** | xUnit v3 | 3.2.2 | +| **Tests Frontend** | Karma + Jasmine | 6.4 / 5.2 | -The authentication architecture is secure, extensible, and aligned with enterprise best practices. +--- -## 🚀 Getting Started +## Features -### Prerequisites +- **Registrierung & Login** mit JWT-Token (Access + Refresh Token) +- **Passwort-Reset** per E-Mail (MailKit, HTML-Template) +- **Rollensystem**: SuperAdmin, Admin, User +- **Benutzerverwaltung**: CRUD, Rollenzuweisung/-entzug +- **Paginierte Benutzerliste** +- **Dark Mode** +- **Toast-Benachrichtigungen** +- **Route-Guards** (Authenticated, Admin, Guest) +- **Token-Interceptor** mit automatischem Refresh bei 401 + +--- + +## Voraussetzungen - [.NET 10 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) - -- [Node.js (LTS)](https://nodejs.org/) - -- [Angular CLI ](https://angular.dev/) - +- [Node.js (LTS)](https://nodejs.org/) (≄18) +- [Angular CLI](https://angular.dev/tools/cli) +- PostgreSQL (lokal oder per Docker) +- Ein Chrome/Chromium fĂŒr Headless-Tests -### 🔧 Setup Instructions +--- -1. **Install Angular dependencies** - - ⚠ **Before starting the application for the first time**, run the following command in the `ClientApp` directory: - ```bash - cd src/ClientApp - npm install - ``` - -2. **Run the application** - - Go back to the API folder and start the backend server: - ```bash - cd src/API - dotnet run --launch-profile angular_dev - ``` - -3. **Access the app** - - - Angular frontend: [http://localhost:44492](http://localhost:44492) - - - Swagger UI (API docs): [http://localhost:5184/swagger](http://localhost:5184/swagger) +## Setup - --- +### 1. Repository klonen -## đŸ§Ș Running Angular Tests +```bash +git clone +cd DotNetAngular +``` -Angular frontend tests are set up using Karma + Jasmine. All test files follow the `.spec.ts` naming convention and are located alongside their respective components and services in `src/ClientApp/src/`. +### 2. Angular-AbhĂ€ngigkeiten installieren -### Quick Start +```bash +cd src/ClientApp +npm install +cd ../.. +``` - ```bash - cd src/ClientApp - npm test - ``` +### 3. Datenbank konfigurieren -### Headless/CI und Coverage +Die Verbindungsdaten werden ĂŒber **User Secrets** gesetzt: - ```bash - cd src/ClientApp - npm run test:coverage - ``` -The coverage report is generated under `src/ClientApp/coverage/` (HTML-Report in `index.html`). +```bash +cd src/API +dotnet user-secrets set "PostgreSqlSettings:Password" "" +dotnet user-secrets set "PostgreSqlSettings:Host" "localhost" +dotnet user-secrets set "EmailSettings:From" "deine-email@gmail.com" +dotnet user-secrets set "EmailSettings:Password" "" +``` - ```bash - cd src/ClientApp - npm run test:ci - ``` -Note: For headless tests, a Chrome/Chromium runtime must be present on the machine. +> Standard-Datenbank: `template_db`, Standard-User: `natlinux` (aus `appsettings.json`). + +### 4. Datenbank erstellen + +```bash +dotnet ef database update +``` + +> `dotnet-ef` ist als lokales Tool in `dotnet-tools.json` definiert. + +### 5. Anwendung starten + +**Mit Angular-Dev-Proxy (empfohlen):** + +```bash +cd src/API +dotnet run --launch-profile Angular_dev +``` + +- Angular Frontend: [http://localhost:44492](http://localhost:44492) +- API (direkt): [http://localhost:5184](http://localhost:5184) / [https://localhost:7091](https://localhost:7091) + +**Nur API mit Swagger:** + +```bash +dotnet run --launch-profile swagger_dev +``` + +- Swagger UI: [https://localhost:7091/swagger](https://localhost:7091/swagger) + +--- + +## Tests + +### Backend (xUnit) + +```bash +# Alle Tests +dotnet test + +# Nur Unit-Tests +dotnet test tests/Application.UnitTest + +# Nur Funktionstests +dotnet test tests/Application.FunctionalTest +``` + +### Frontend (Karma + Jasmine) + +```bash +cd src/ClientApp + +# Interaktiv (Browser) +npm test + +# Headless CI +npm run test:ci + +# Mit Coverage +npm run test:coverage +# Report: src/ClientApp/coverage/index.html +``` + +--- + +## NPM-Skripte (Frontend) + +| Befehl | Beschreibung | +|---|---| +| `npm start` | `ng serve --port 44492` | +| `npm run build` | Produktions-Build | +| `npm test` | Tests (Watch-Modus) | +| `npm run test:ci` | Headless CI-Tests | +| `npm run test:coverage` | Tests mit Coverage | +| `npm run watch` | Dev-Build mit Watch | + +--- + +## API-Endpunkte + +Basis: `http://localhost:5184/api` (Dev) / `https://localhost:7091/api` + +### Auth (`/api/auth`) + +| Methode | Pfad | Auth | Beschreibung | +|---|---|---|---| +| POST | `/register` | - | Benutzer registrieren | +| POST | `/login` | - | Login (gibt JWT + Refresh-Token) | +| POST | `/refresh-token` | - | Access-Token erneuern | +| POST | `/send-reset-email/{email}` | - | Passwort-Reset-Mail senden | +| POST | `/reset-password` | - | Passwort zurĂŒcksetzen | + +### User (`/api/user`) + +| Methode | Pfad | Auth | Beschreibung | +|---|---|---|---| +| GET | `/` | Admin/SuperAdmin | Paginierte Benutzerliste | +| GET | `/{id}` | Authenticated | Benutzer per ID | +| PUT | `/` | Authenticated | Benutzer aktualisieren | +| DELETE | `/{id}` | Authenticated | Benutzer löschen (außer sich selbst) | +| POST | `/assign-role` | SuperAdmin | Rolle zuweisen | +| DELETE | `/revoke-role` | SuperAdmin | Rolle entziehen | + +--- + +## Standard-Rollen + +| Rolle | ID | Berechtigungen | +|---|---|---| +| SuperAdmin | 1 | Vollzugriff, Rollenverwaltung | +| Admin | 2 | Benutzerliste, Dashboard | +| User | 3 | Eigenes Dashboard, Profil bearbeiten | + +Seed-Daten: SuperAdmin (ID 1) und Admin (ID 2) werden automatisch angelegt. + +--- + +## Docker + +Es ist kein Docker-Setup im Projekt enthalten. FĂŒr PostgreSQL kann ein Docker-Container gestartet werden: + +```bash +docker run -d --name postgres -e POSTGRES_PASSWORD= -p 5432:5432 postgres:17 +``` + +--- + +## Hinweise + +- Der JWT-Secret-Key in `appsettings.json` ist ein Platzhalter und sollte in Produktion ĂŒberschrieben werden (z. B. ĂŒber User Secrets, Umgebungsvariablen oder `appsettings.Production.json`). +- E-Mail-Versand verwendet Gmail SMTP – es wird ein [App-Passwort](https://support.google.com/accounts/answer/185833) benötigt. +- Der ClientApp-README (generiert von Angular CLI) wurde auf die tatsĂ€chliche Angular-Version aktualisiert. diff --git a/src/ClientApp/package-lock.json b/src/ClientApp/package-lock.json index acc8ba3..1beada3 100644 --- a/src/ClientApp/package-lock.json +++ b/src/ClientApp/package-lock.json @@ -18,7 +18,6 @@ "@angular/platform-browser-dynamic": "^21.2.4", "@angular/router": "^21.2.4", "@auth0/angular-jwt": "^5.2.0", - "@ng-bootstrap/ng-bootstrap": "^20.0.0", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", "rxjs": "~7.8.0", @@ -2330,22 +2329,6 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@ng-bootstrap/ng-bootstrap": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-20.0.0.tgz", - "integrity": "sha512-Jt+GUQ0PdM8VsOUUVr7vTQXhwcGwe2DCe1mmfS21vz9pLSOtGRz41ohZKc1egUevj5Rxm2sHVq5Sve68/nTMfA==", - "dependencies": { - "tslib": "^2.3.0" - }, - "peerDependencies": { - "@angular/common": "^21.0.0", - "@angular/core": "^21.0.0", - "@angular/forms": "^21.0.0", - "@angular/localize": "^21.0.0", - "@popperjs/core": "^2.11.8", - "rxjs": "^6.5.3 || ^7.4.0" - } - }, "node_modules/@npmcli/agent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", diff --git a/src/ClientApp/package.json b/src/ClientApp/package.json index d217390..16f3cab 100644 --- a/src/ClientApp/package.json +++ b/src/ClientApp/package.json @@ -22,7 +22,6 @@ "@angular/platform-browser-dynamic": "^21.2.4", "@angular/router": "^21.2.4", "@auth0/angular-jwt": "^5.2.0", - "@ng-bootstrap/ng-bootstrap": "^20.0.0", "bootstrap": "^5.3.8", "bootstrap-icons": "^1.13.1", "rxjs": "~7.8.0", diff --git a/src/ClientApp/src/app/infrastructure/services/auth-service.ts b/src/ClientApp/src/app/infrastructure/services/auth-service.ts index 273dd23..a549031 100644 --- a/src/ClientApp/src/app/infrastructure/services/auth-service.ts +++ b/src/ClientApp/src/app/infrastructure/services/auth-service.ts @@ -37,7 +37,8 @@ export class AuthService implements IAuthService { signOut() { if (this.isBrowser) { - localStorage.clear(); + localStorage.removeItem('token'); + localStorage.removeItem('auth/refresh-token'); this.router.navigate(['/login']).catch(error => { console.error('Navigation error:', error); }); diff --git a/src/ClientApp/src/app/infrastructure/services/toast.service.ts b/src/ClientApp/src/app/infrastructure/services/toast.service.ts index 19f94e0..84591a8 100644 --- a/src/ClientApp/src/app/infrastructure/services/toast.service.ts +++ b/src/ClientApp/src/app/infrastructure/services/toast.service.ts @@ -8,7 +8,9 @@ export class ToastService { toasts: Toast[] = []; show(message: string, options: Partial = {}) { - this.toasts.push({message, ...options}); + const toast: Toast = {message, ...options}; + this.toasts.push(toast); + setTimeout(() => this.remove(toast), toast.delay || 5000); } remove(toast: Toast) { diff --git a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.html b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.html index dfe4614..a93f653 100644 --- a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.html +++ b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.html @@ -21,6 +21,6 @@ Loading... } Send - + diff --git a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.spec.ts b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.spec.ts index 96b27b1..ad97d63 100644 --- a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.spec.ts +++ b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.spec.ts @@ -1,6 +1,5 @@ import {ComponentFixture, TestBed} from '@angular/core/testing'; import {ForgetPasswordPopupComponent} from './forget-password-popup.component'; -import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap'; import {ResetPasswordService} from '../../../infrastructure/services/reset-password.service'; import {ToastService} from '../../../infrastructure/services/toast.service'; import {LoadingService} from '../../../infrastructure/services/loading.service'; @@ -11,19 +10,16 @@ describe('ForgetPasswordPopupComponent', () => { let fixture: ComponentFixture; let resetService: jasmine.SpyObj; let toastService: jasmine.SpyObj; - let activeModal: jasmine.SpyObj; beforeEach(async () => { resetService = jasmine.createSpyObj('ResetPasswordService', ['sendResetPasswordLink']); toastService = jasmine.createSpyObj('ToastService', ['show']); - activeModal = jasmine.createSpyObj('NgbActiveModal', ['close']); await TestBed.configureTestingModule({ imports: [ForgetPasswordPopupComponent], providers: [ {provide: ResetPasswordService, useValue: resetService}, {provide: ToastService, useValue: toastService}, - {provide: NgbActiveModal, useValue: activeModal}, LoadingService, ] }).compileComponents(); diff --git a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.ts b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.ts index 98255f3..82af58d 100644 --- a/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.ts +++ b/src/ClientApp/src/app/presentation/authentication/forget-password-popup/forget-password-popup.component.ts @@ -1,6 +1,5 @@ -import {Component, inject} from '@angular/core'; +import {Component, EventEmitter, inject, Output} from '@angular/core'; import {FormsModule} from "@angular/forms"; -import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; import {ResetPasswordService} from "../../../infrastructure/services/reset-password.service"; import {ToastService} from "../../../infrastructure/services/toast.service"; import {LoadingService} from "../../../infrastructure/services/loading.service"; @@ -17,13 +16,12 @@ export class ForgetPasswordPopupComponent { public resetPasswordEmail!: string; public isValidEmail!: boolean; - activeModal = inject(NgbActiveModal); + @Output() close = new EventEmitter(); + resetService = inject(ResetPasswordService); toastService = inject(ToastService); loadingService = inject(LoadingService); - constructor() { } - checkValidEmail(event: string) { const value = event; const emailPattern = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,3}$/; @@ -45,7 +43,7 @@ export class ForgetPasswordPopupComponent { delay: 3000 }); this.resetPasswordEmail = ""; - this.activeModal.close(); + this.close.emit(); }, error: (err) => { this.loadingService.hide(); diff --git a/src/ClientApp/src/app/presentation/authentication/login/login.component.html b/src/ClientApp/src/app/presentation/authentication/login/login.component.html index 58d981a..4dfb318 100644 --- a/src/ClientApp/src/app/presentation/authentication/login/login.component.html +++ b/src/ClientApp/src/app/presentation/authentication/login/login.component.html @@ -10,13 +10,16 @@
+ autocomplete="email" class="form-control" formControlName="email" id="email" + placeholder="E-Mail" + type="email">
- @if (loginForm.controls['email'].dirty && loginForm.hasError('pattern' , 'email')) { - *invalid E-Mail + @if (loginForm.controls['email'].touched && loginForm.controls['email'].errors) { + @if (loginForm.controls['email'].errors['required']) { + *Email is required + } @else if (loginForm.controls['email'].errors['pattern']) { + *invalid E-Mail + } }
@@ -28,15 +31,24 @@ placeholder="Password">
- @if (loginForm.controls['password'].dirty && loginForm.hasError('required' , 'password')) { - *Password is required + @if (loginForm.controls['password'].touched && loginForm.controls['password'].errors) { + @if (loginForm.controls['password'].errors['required']) { + *Password is required + } }

Forgot Password?

+@if (showForgotPasswordPopup) { + + +}

You donÂŽt have an account? Sign - up here!

+ class="fw-bold text-body">Sign + up here!

- - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/ClientApp/src/app/presentation/authentication/login/login.component.spec.ts b/src/ClientApp/src/app/presentation/authentication/login/login.component.spec.ts index e898134..af654c3 100644 --- a/src/ClientApp/src/app/presentation/authentication/login/login.component.spec.ts +++ b/src/ClientApp/src/app/presentation/authentication/login/login.component.spec.ts @@ -5,7 +5,6 @@ import {ToastService} from '../../../infrastructure/services/toast.service'; import {UserStoreService} from '../../../infrastructure/services/user-store.service'; import {LoadingService} from '../../../infrastructure/services/loading.service'; import {ActivatedRoute, Router} from '@angular/router'; -import {NgbModal} from '@ng-bootstrap/ng-bootstrap'; import {of, throwError} from 'rxjs'; describe('LoginComponent', () => { @@ -19,7 +18,6 @@ describe('LoginComponent', () => { authService = jasmine.createSpyObj('AuthService', ['login', 'storeToken', 'storeRefreshToken', 'decodedToken']); router = jasmine.createSpyObj('Router', ['navigate']); toastService = jasmine.createSpyObj('ToastService', ['show']); - const modalService = jasmine.createSpyObj('NgbModal', ['open']); await TestBed.configureTestingModule({ imports: [LoginComponent], @@ -27,7 +25,6 @@ describe('LoginComponent', () => { {provide: AuthService, useValue: authService}, {provide: Router, useValue: router}, {provide: ToastService, useValue: toastService}, - {provide: NgbModal, useValue: modalService}, {provide: ActivatedRoute, useValue: {snapshot: {data: {}}, firstChild: null}}, UserStoreService, LoadingService, diff --git a/src/ClientApp/src/app/presentation/authentication/login/login.component.ts b/src/ClientApp/src/app/presentation/authentication/login/login.component.ts index 0feccf5..e21b094 100644 --- a/src/ClientApp/src/app/presentation/authentication/login/login.component.ts +++ b/src/ClientApp/src/app/presentation/authentication/login/login.component.ts @@ -5,7 +5,6 @@ import ValidateForm from "../../../infrastructure/utilities/validate-form"; import {AuthService} from "../../../infrastructure/services/auth-service"; import {ToastService} from "../../../infrastructure/services/toast.service"; import {UserStoreService} from "../../../infrastructure/services/user-store.service"; -import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; import {ForgetPasswordPopupComponent} from "../forget-password-popup/forget-password-popup.component"; import {LoadingService} from "../../../infrastructure/services/loading.service"; import {Title} from "@angular/platform-browser"; @@ -14,7 +13,8 @@ import {Title} from "@angular/platform-browser"; selector: 'app-login', imports: [ ReactiveFormsModule, - RouterLink + RouterLink, + ForgetPasswordPopupComponent ], templateUrl: './login.component.html', standalone: true, @@ -26,13 +26,13 @@ export class LoginComponent { type: string = "password"; isText: boolean = true; eyeIcon: string = "bi-eye-slash" + showForgotPasswordPopup = false; loadingService = inject(LoadingService); authenticateService = inject(AuthService) toastService = inject(ToastService); userStore = inject(UserStoreService); private router = inject(Router) - modalService = inject(NgbModal) title = inject(Title) constructor(private formBuilder: FormBuilder) { @@ -49,7 +49,11 @@ export class LoginComponent { } openPopup() { - this.modalService.open(ForgetPasswordPopupComponent, {centered: true ,backdrop: 'static' }); + this.showForgotPasswordPopup = true; + } + + closePopup() { + this.showForgotPasswordPopup = false; } onLogin() { diff --git a/src/ClientApp/src/app/presentation/authentication/register/register.component.html b/src/ClientApp/src/app/presentation/authentication/register/register.component.html index a868219..52f6dd6 100644 --- a/src/ClientApp/src/app/presentation/authentication/register/register.component.html +++ b/src/ClientApp/src/app/presentation/authentication/register/register.component.html @@ -17,20 +17,14 @@
@if (registerForm.controls['username'].touched && registerForm.controls['username'].errors) { - @if (registerForm.controls['username'].errors['minlength']) { - - *Username must be at least 3 characters long - - } - @if (registerForm.controls['username'].errors['maxlength']) { - - *Username could contain max 20 characters long - - } - @if (registerForm.controls['username'].errors['pattern']) { - - *Username could contain only letters, numbers and underscore - + @if (registerForm.controls['username'].errors['required']) { + *Username is required + } @else if (registerForm.controls['username'].errors['minlength']) { + *Username must be at least 3 characters long + } @else if (registerForm.controls['username'].errors['maxlength']) { + *Username could contain max 20 characters long + } @else if (registerForm.controls['username'].errors['pattern']) { + *Username could contain only letters, numbers and underscore } }
@@ -43,9 +37,12 @@ name="email" placeholder="E-Mail" type="email"> - @if (registerForm.controls['email'].dirty && registerForm.hasError('pattern' , 'email')) { - *invalid E-Mail + @if (registerForm.controls['email'].touched && registerForm.controls['email'].errors) { + @if (registerForm.controls['email'].errors['required']) { + *Email is required + } @else if (registerForm.controls['email'].errors['pattern']) { + *invalid E-Mail + } } @@ -60,64 +57,52 @@
@if (registerForm.controls['password'].touched && registerForm.controls['password'].errors) { - @if (registerForm.controls['password'].errors['minlength']) { - - *Password must be at least 10 characters long - - } - @if (registerForm.controls['password'].errors['uppercase']) { - - *Password must contain at least one uppercase letter - - } - @if (registerForm.controls['password'].errors['lowercase']) { - - *Password must contain at least one lowercase letter - - } - @if (registerForm.controls['password'].errors['number']) { - - *Password must contain at least one number - - } - @if (registerForm.controls['password'].errors['special']) { - - *Password must contain at least one special character - + @if (registerForm.controls['password'].errors['required']) { + *Password is required + } @else if (registerForm.controls['password'].errors['minlength']) { + *Password must be at least 10 characters long + } @else if (registerForm.controls['password'].errors['uppercase']) { + *Password must contain at least one uppercase letter + } @else if (registerForm.controls['password'].errors['lowercase']) { + *Password must contain at least one lowercase letter + } @else if (registerForm.controls['password'].errors['number']) { + *Password must contain at least one number + } @else if (registerForm.controls['password'].errors['special']) { + *Password must contain at least one special character } }
- - - - - - - - - - - - - - + + + + + + + + + + + + + +

Have already an account? Sign - in here!

+ class="fw-bold text-body">Sign + in here!

@@ -125,4 +110,3 @@ - diff --git a/src/ClientApp/src/app/presentation/shared/toast/toast.component.html b/src/ClientApp/src/app/presentation/shared/toast/toast.component.html index c1d280c..688eaa4 100644 --- a/src/ClientApp/src/app/presentation/shared/toast/toast.component.html +++ b/src/ClientApp/src/app/presentation/shared/toast/toast.component.html @@ -1,9 +1,8 @@ @for (toast of toastService.toasts; track toast) { - - {{ toast.message }} - + } diff --git a/src/ClientApp/src/app/presentation/shared/toast/toast.component.scss b/src/ClientApp/src/app/presentation/shared/toast/toast.component.scss index 956444b..f05f46c 100644 --- a/src/ClientApp/src/app/presentation/shared/toast/toast.component.scss +++ b/src/ClientApp/src/app/presentation/shared/toast/toast.component.scss @@ -1,4 +1,4 @@ -ngb-toast { +.toast { position: fixed; top: 20px; right: -100%; // Start außerhalb des Bildschirms diff --git a/src/ClientApp/src/app/presentation/shared/toast/toast.component.ts b/src/ClientApp/src/app/presentation/shared/toast/toast.component.ts index 0dffc23..65e4d9e 100644 --- a/src/ClientApp/src/app/presentation/shared/toast/toast.component.ts +++ b/src/ClientApp/src/app/presentation/shared/toast/toast.component.ts @@ -1,13 +1,10 @@ import {Component, inject} from '@angular/core'; import {ToastService} from "../../../infrastructure/services/toast.service"; -import {NgbToast} from "@ng-bootstrap/ng-bootstrap"; @Component({ selector: 'app-toast', - imports: [ - NgbToast - ], + imports: [], templateUrl: './toast.component.html', standalone: true, styleUrls: ['./toast.component.scss']