From e09110346e3733c198decc1fba44ddc6a08777e3 Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Mon, 10 Nov 2025 17:33:05 +0100 Subject: [PATCH] add login component and auth service --- admin/src/app/app.config.ts | 19 +- admin/src/app/app.html | 345 +----------------- admin/src/app/app.routes.ts | 9 +- admin/src/app/app.ts | 13 +- admin/src/app/auth/auth.guard.ts | 23 ++ admin/src/app/auth/auth.service.ts | 36 ++ admin/src/app/auth/jwt.interceptor.ts | 29 ++ .../src/app/components/home/home.component.ts | 8 + .../app/components/login/login.component.html | 15 + .../app/components/login/login.component.ts | 36 ++ .../app/components/main-menu/main-menu.css | 0 .../app/components/main-menu/main-menu.html | 1 + .../components/main-menu/main-menu.spec.ts | 23 ++ .../src/app/components/main-menu/main-menu.ts | 11 + 14 files changed, 218 insertions(+), 350 deletions(-) create mode 100644 admin/src/app/auth/auth.guard.ts create mode 100644 admin/src/app/auth/auth.service.ts create mode 100644 admin/src/app/auth/jwt.interceptor.ts create mode 100644 admin/src/app/components/home/home.component.ts create mode 100644 admin/src/app/components/login/login.component.html create mode 100644 admin/src/app/components/login/login.component.ts create mode 100644 admin/src/app/components/main-menu/main-menu.css create mode 100644 admin/src/app/components/main-menu/main-menu.html create mode 100644 admin/src/app/components/main-menu/main-menu.spec.ts create mode 100644 admin/src/app/components/main-menu/main-menu.ts diff --git a/admin/src/app/app.config.ts b/admin/src/app/app.config.ts index 2e06ce8..3e5a0b8 100644 --- a/admin/src/app/app.config.ts +++ b/admin/src/app/app.config.ts @@ -1,12 +1,19 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZonelessChangeDetection } from '@angular/core'; +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter } from '@angular/router'; - +import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { routes } from './app.routes'; +import { JwtInterceptor } from './auth/jwt.interceptor'; +import { AuthService } from './auth/auth.service'; +import { AuthGuard } from './auth/auth.guard'; export const appConfig: ApplicationConfig = { providers: [ - provideBrowserGlobalErrorListeners(), - provideZonelessChangeDetection(), - provideRouter(routes) - ] + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideHttpClient(withInterceptorsFromDi()), + AuthService, + AuthGuard, + { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, + ], }; diff --git a/admin/src/app/app.html b/admin/src/app/app.html index 7528372..4dbe0a4 100644 --- a/admin/src/app/app.html +++ b/admin/src/app/app.html @@ -1,342 +1,5 @@ - - - - - - - - - - - -
-
-
- -

Hello, {{ title() }}

-

Congratulations! Your app is running. 🎉

-
- -
-
- @for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'}, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, - ]; track item.title) { - - {{ item.title }} - - - - - } -
- -
-
-
- - - - - - - - - - +
+ +
+ diff --git a/admin/src/app/app.routes.ts b/admin/src/app/app.routes.ts index dc39edb..b4405e1 100644 --- a/admin/src/app/app.routes.ts +++ b/admin/src/app/app.routes.ts @@ -1,3 +1,10 @@ import { Routes } from '@angular/router'; +import { LoginComponent } from './components/login/login.component'; +import { AuthGuard } from './auth/auth.guard'; +import { HomeComponent } from './components/home/home.component'; // Assuming you have a HomeComponent -export const routes: Routes = []; +export const routes: Routes = [ + { path: 'login', component: LoginComponent }, + { path: '', component: HomeComponent, canActivate: [AuthGuard] }, + { path: '**', redirectTo: '' } // Redirect to home for any other route +]; diff --git a/admin/src/app/app.ts b/admin/src/app/app.ts index 7c72c5c..6d9ecf6 100644 --- a/admin/src/app/app.ts +++ b/admin/src/app/app.ts @@ -1,12 +1,21 @@ import { Component, signal } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { Router, RouterOutlet } from '@angular/router'; +import { MainMenu } from './components/main-menu/main-menu'; +import { AuthService } from './auth/auth.service'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, MainMenu], templateUrl: './app.html', styleUrl: './app.css' }) export class App { protected readonly title = signal('admin'); + + constructor(private authService: AuthService, private router: Router) {} + + logout(): void { + this.authService.logout(); + this.router.navigate(['/login']); + } } diff --git a/admin/src/app/auth/auth.guard.ts b/admin/src/app/auth/auth.guard.ts new file mode 100644 index 0000000..90a754c --- /dev/null +++ b/admin/src/app/auth/auth.guard.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean | UrlTree { + if (this.authService.isLoggedIn()) { + return true; + } else { + // Redirect to the login page + return this.router.createUrlTree(['/login']); + } + } +} diff --git a/admin/src/app/auth/auth.service.ts b/admin/src/app/auth/auth.service.ts new file mode 100644 index 0000000..52079da --- /dev/null +++ b/admin/src/app/auth/auth.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthService { + private readonly TOKEN_KEY = 'access_token'; + private apiUrl = 'http://localhost:3000/auth'; // Adjust if your server URL is different + + constructor(private http: HttpClient) {} + + login(credentials: { username: string; password: string }): Observable { + return this.http.post<{ access_token: string }>(`${this.apiUrl}/login`, credentials).pipe( + tap((response) => this.setToken(response.access_token)) + ); + } + + logout(): void { + localStorage.removeItem(this.TOKEN_KEY); + } + + getToken(): string | null { + return localStorage.getItem(this.TOKEN_KEY); + } + + isLoggedIn(): boolean { + return this.getToken() !== null; + } + + private setToken(token: string): void { + localStorage.setItem(this.TOKEN_KEY, token); + } +} diff --git a/admin/src/app/auth/jwt.interceptor.ts b/admin/src/app/auth/jwt.interceptor.ts new file mode 100644 index 0000000..96d864a --- /dev/null +++ b/admin/src/app/auth/jwt.interceptor.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { AuthService } from './auth.service'; + +@Injectable() +export class JwtInterceptor implements HttpInterceptor { + constructor(private authService: AuthService) {} + + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + const token = this.authService.getToken(); + if (token) { + request = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}`, + }, + }); + } + return next.handle(request); + } +} diff --git a/admin/src/app/components/home/home.component.ts b/admin/src/app/components/home/home.component.ts new file mode 100644 index 0000000..39e92b3 --- /dev/null +++ b/admin/src/app/components/home/home.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-home', + template: '

Welcome to the Admin Panel!

', + standalone: true, +}) +export class HomeComponent {} diff --git a/admin/src/app/components/login/login.component.html b/admin/src/app/components/login/login.component.html new file mode 100644 index 0000000..f12c5a1 --- /dev/null +++ b/admin/src/app/components/login/login.component.html @@ -0,0 +1,15 @@ +
+

Login

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

{{ errorMessage }}

+
diff --git a/admin/src/app/components/login/login.component.ts b/admin/src/app/components/login/login.component.ts new file mode 100644 index 0000000..a77faea --- /dev/null +++ b/admin/src/app/components/login/login.component.ts @@ -0,0 +1,36 @@ +import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AuthService } from '../../auth/auth.service'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + standalone: true, + imports: [ReactiveFormsModule, CommonModule], +}) +export class LoginComponent { + loginForm: FormGroup; + errorMessage: string = ''; + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private router: Router + ) { + this.loginForm = this.fb.group({ + username: ['', Validators.required], + password: ['', Validators.required], + }); + } + + onSubmit(): void { + if (this.loginForm.valid) { + this.authService.login(this.loginForm.value).subscribe({ + next: () => this.router.navigate(['/']), + error: (err) => (this.errorMessage = 'Invalid username or password'), + }); + } + } +} diff --git a/admin/src/app/components/main-menu/main-menu.css b/admin/src/app/components/main-menu/main-menu.css new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/components/main-menu/main-menu.html b/admin/src/app/components/main-menu/main-menu.html new file mode 100644 index 0000000..28bd6d6 --- /dev/null +++ b/admin/src/app/components/main-menu/main-menu.html @@ -0,0 +1 @@ +

main-menu works!

diff --git a/admin/src/app/components/main-menu/main-menu.spec.ts b/admin/src/app/components/main-menu/main-menu.spec.ts new file mode 100644 index 0000000..86c5590 --- /dev/null +++ b/admin/src/app/components/main-menu/main-menu.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MainMenu } from './main-menu'; + +describe('MainMenu', () => { + let component: MainMenu; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MainMenu] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MainMenu); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/components/main-menu/main-menu.ts b/admin/src/app/components/main-menu/main-menu.ts new file mode 100644 index 0000000..abdd861 --- /dev/null +++ b/admin/src/app/components/main-menu/main-menu.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-main-menu', + imports: [], + templateUrl: './main-menu.html', + styleUrl: './main-menu.css', +}) +export class MainMenu { + +}