diff --git a/src/app/app.config.ts b/src/app/app.config.ts index cb1270e..ce7a77b 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -1,11 +1,38 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; +import { ApplicationConfig, provideBrowserGlobalErrorListeners, APP_INITIALIZER } from '@angular/core'; import { provideRouter } from '@angular/router'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { catchError, of } from 'rxjs'; import { routes } from './app.routes'; +import { httpInterceptor } from './interceptors/http.interceptor'; +import { AuthService } from './services/auth.service'; + +/** + * Initialize auth state on app startup by checking for existing session + */ +function initializeAuth(authService: AuthService) { + return () => authService.checkSession().pipe( + catchError((error) => { + // Session check failed - user is not logged in or session expired + console.log('No active session or session expired'); + authService.clearAuthState(); + return of(null); + }) + ); +} export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), - provideRouter(routes) + provideRouter(routes), + provideHttpClient( + withInterceptors([httpInterceptor]) + ), + { + provide: APP_INITIALIZER, + useFactory: initializeAuth, + deps: [AuthService], + multi: true + } ] }; diff --git a/src/app/app.html b/src/app/app.html index e0118a1..41056f6 100644 --- a/src/app/app.html +++ b/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/src/app/app.routes.ts b/src/app/app.routes.ts index dc39edb..5efd1c7 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,3 +1,18 @@ import { Routes } from '@angular/router'; +import { authGuard } from './guards/auth.guard'; +import { LoginComponent } from './pages/login/login.component'; -export const routes: Routes = []; +export const routes: Routes = [ + // Public routes + { + path: 'login', + component: LoginComponent + }, + + // Protected routes - require authentication + { + path: '', + canActivate: [authGuard], + loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent) + } +]; diff --git a/src/app/app.ts b/src/app/app.ts index 2cb2218..4b39967 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,12 +1,17 @@ -import { Component, signal } from '@angular/core'; +import { Component, signal, inject } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { AuthService } from './services/auth.service'; +import { CommonModule } from '@angular/common'; +import { Navbar } from './navbar/navbar'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, CommonModule, Navbar], templateUrl: './app.html', styleUrl: './app.css' }) export class App { protected readonly title = signal('KanbanCloneAngular'); + protected readonly description = signal('A simple Kanban board application built with Angular'); + protected authService = inject(AuthService); } diff --git a/src/app/interceptors/http.interceptor.ts b/src/app/interceptors/http.interceptor.ts new file mode 100644 index 0000000..0925840 --- /dev/null +++ b/src/app/interceptors/http.interceptor.ts @@ -0,0 +1,47 @@ +import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { Router } from '@angular/router'; +import { catchError, throwError } from 'rxjs'; + +/** + * HTTP Interceptor that: + * - Adds withCredentials to all requests (enables cookie sending/receiving) + * - Handles global HTTP errors + * - Redirects to login on 401 Unauthorized + */ +export const httpInterceptor: HttpInterceptorFn = (req, next) => { + const router = inject(Router); + + // Clone the request to add withCredentials flag + // This ensures cookies are sent with every request + const reqWithCredentials = req.clone({ + withCredentials: true + }); + + // Pass the cloned request to the next handler + return next(reqWithCredentials).pipe( + catchError((error: HttpErrorResponse) => { + // Handle different HTTP error codes + if (error.status === 401) { + // Unauthorized - redirect to login (but not for session check endpoint) + // Skip redirect for /me endpoint to avoid issues during app initialization + if (!req.url.endsWith('/me')) { + console.error('Unauthorized access - redirecting to login'); + router.navigate(['/login']); + } + } else if (error.status === 403) { + // Forbidden + console.error('Access forbidden:', error.message); + } else if (error.status === 0) { + // Network error + console.error('Network error - check if the server is running'); + } else { + // Other errors + console.error(`HTTP Error ${error.status}:`, error.message); + } + + // Re-throw the error so components can handle it if needed + return throwError(() => error); + }) + ); +}; diff --git a/src/styles.css b/src/styles.css index 90d4ee0..648193c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,18 @@ /* You can add global styles to this file, and also import other style files */ +:root{ + --background-color: whitesmoke; + --primary-color: #4a90e2; + --secondary-color: #f5f5f5; + --text-color: black; +} + +body { + margin: 0; + + width: 100%; + height: fit-content; + + font-family: Arial, sans-serif; + background-color: var(--background-color); + color: var(--text-color); +} \ No newline at end of file