diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index ce7a77b..979665a 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,7 +1,11 @@
-import { ApplicationConfig, provideBrowserGlobalErrorListeners, APP_INITIALIZER } 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 { catchError, of, throwError } from 'rxjs';
import { routes } from './app.routes';
import { httpInterceptor } from './interceptors/http.interceptor';
@@ -11,28 +15,30 @@ 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);
- })
- );
+ return () =>
+ authService.checkSession().pipe(
+ catchError((error) => {
+ console.error('Session check failed:', error);
+ if (error?.status === 401 || error?.status === 422) {
+ authService.clearAuthState();
+ return of(null);
+ }
+ authService.clearAuthState();
+ return throwError(() => error);
+ }),
+ );
}
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
- provideHttpClient(
- withInterceptors([httpInterceptor])
- ),
+ provideHttpClient(withInterceptors([httpInterceptor])),
{
provide: APP_INITIALIZER,
useFactory: initializeAuth,
deps: [AuthService],
- multi: true
- }
- ]
+ multi: true,
+ },
+ ],
};
diff --git a/src/app/app.html b/src/app/app.html
index c9f68e3..363c8a6 100644
--- a/src/app/app.html
+++ b/src/app/app.html
@@ -1,8 +1,5 @@
-
-
-
+
+
+
-
-
-
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index 5efd1c7..30d1fbd 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -4,15 +4,28 @@ import { LoginComponent } from './pages/login/login.component';
export const routes: Routes = [
// Public routes
- {
- path: 'login',
- component: LoginComponent
+ {
+ path: 'login',
+ component: LoginComponent
},
// Protected routes - require authentication
- {
- path: '',
+ {
+ path: '',
canActivate: [authGuard],
- loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent)
+ children: [
+ {
+ path: '',
+ loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent)
+ },
+ {
+ path: 'projects/new',
+ loadComponent: () => import('./pages/project-create/project-create.component').then(m => m.ProjectCreateComponent)
+ },
+ {
+ path: 'projects/:id',
+ loadComponent: () => import('./pages/project-details/project-details.component').then(m => m.ProjectDetailsComponent)
+ }
+ ]
}
];
diff --git a/src/app/app.ts b/src/app/app.ts
index 4b39967..b974fe7 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -2,11 +2,12 @@ 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';
+import { NavbarComponent } from './components/navbar/navbar.component';
+import { FooterComponent } from './components/footer/footer.component';
@Component({
selector: 'app-root',
- imports: [RouterOutlet, CommonModule, Navbar],
+ imports: [RouterOutlet, CommonModule, NavbarComponent, FooterComponent],
templateUrl: './app.html',
styleUrl: './app.css'
})
diff --git a/src/app/interceptors/http.interceptor.ts b/src/app/interceptors/http.interceptor.ts
index 0925840..1fae508 100644
--- a/src/app/interceptors/http.interceptor.ts
+++ b/src/app/interceptors/http.interceptor.ts
@@ -15,33 +15,24 @@ export const httpInterceptor: HttpInterceptorFn = (req, next) => {
// Clone the request to add withCredentials flag
// This ensures cookies are sent with every request
const reqWithCredentials = req.clone({
- withCredentials: true
+ 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');
+ if (router.url !== '/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/app/models/projects.models.ts b/src/app/models/projects.models.ts
index 2111465..9c80e6d 100644
--- a/src/app/models/projects.models.ts
+++ b/src/app/models/projects.models.ts
@@ -17,7 +17,7 @@ export interface ProjectFull {
export interface CreateProjectRequest {
name: string;
- description?: string;
+ description: string;
}
export interface UpdateProjectRequest {
diff --git a/src/app/pages/project-create/project-create.component.html b/src/app/pages/project-create/project-create.component.html
index 7709931..016ed05 100644
--- a/src/app/pages/project-create/project-create.component.html
+++ b/src/app/pages/project-create/project-create.component.html
@@ -26,7 +26,8 @@
name="description"
[(ngModel)]="description"
rows="4"
- placeholder="Optional description"
+ placeholder="Enter a project description"
+ required
>
diff --git a/src/app/pages/project-create/project-create.component.ts b/src/app/pages/project-create/project-create.component.ts
index fbb7af7..2e1392f 100644
--- a/src/app/pages/project-create/project-create.component.ts
+++ b/src/app/pages/project-create/project-create.component.ts
@@ -10,7 +10,7 @@ import { CreateProjectRequest, Project } from '../../models/projects.models';
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './project-create.component.html',
- styleUrl: './project-create.component.css'
+ styleUrl: './project-create.component.css',
})
export class ProjectCreateComponent {
private apiService = inject(ApiService);
@@ -27,9 +27,14 @@ export class ProjectCreateComponent {
return;
}
+ if (!this.description.trim()) {
+ this.errorMessage.set('Project description is required.');
+ return;
+ }
+
const payload: CreateProjectRequest = {
name: this.name.trim(),
- description: this.description.trim() || undefined
+ description: this.description.trim(),
};
this.isSaving.set(true);
@@ -39,7 +44,7 @@ export class ProjectCreateComponent {
next: (project) => {
this.isSaving.set(false);
if (project?.id != null) {
- this.router.navigate(['/projects', project.id]);
+ this.router.navigate(['/projects/', project.id]);
} else {
this.router.navigate(['/']);
}
@@ -47,9 +52,9 @@ export class ProjectCreateComponent {
error: (error) => {
this.isSaving.set(false);
this.errorMessage.set(
- error?.error?.message || 'Failed to create project. Please try again.'
+ error?.error?.message || 'Failed to create project. Please try again.',
);
- }
+ },
});
}
diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts
index 18f3d00..765bc2c 100644
--- a/src/app/services/auth.service.ts
+++ b/src/app/services/auth.service.ts
@@ -1,6 +1,6 @@
import { Injectable, inject, signal, computed } from '@angular/core';
import { HttpClient } from '@angular/common/http';
-import { Observable, tap } from 'rxjs';
+import { Observable, tap, catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';
import { LoginRequest, LoginResponse, User, AuthState } from '../models/auth.models';
import { environment } from '../config/environment';
@@ -36,11 +36,11 @@ export class AuthService {
credentials
).pipe(
tap(response => {
- if (response.success && response.user) {
- // Update auth state
+ if (response.success || response.user) {
+ // Update auth state even if the backend only sets a cookie
this.authState.set({
isAuthenticated: true,
- user: response.user
+ user: response.user ?? null
});
}
})
@@ -52,7 +52,7 @@ export class AuthService {
* Clears the session cookie on the backend
*/
logout(): Observable {
- return this.http.post(`${environment.apiBaseUrl}/me/logout`, {}).pipe(
+ return this.http.get(`${environment.apiBaseUrl}/me/logout`).pipe(
tap(() => {
// Clear auth state
this.authState.set({
@@ -61,6 +61,11 @@ export class AuthService {
});
// Redirect to login
this.router.navigate(['/login']);
+ }),
+ catchError((error) => {
+ // Even if logout fails on backend, clear local state
+ this.clearAuthState();
+ return throwError(() => error);
})
);
}
@@ -88,5 +93,6 @@ export class AuthService {
isAuthenticated: false,
user: null
});
+ this.router.navigate(['/login']);
}
}