From ac2c2a29bd309e1f82cc018a303cfca6bad50e84 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:07:38 +0300 Subject: [PATCH 01/21] Create and Ignore env files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c0d1f23..e178245 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ testem.log # System files .DS_Store Thumbs.db + +# Environment files +/environments From a412fa39ae24d9ebe2b3293f2a1e4dccdd4ae593 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:12:55 +0300 Subject: [PATCH 02/21] Create default environment file --- src/environments/environment.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/environments/environment.ts diff --git a/src/environments/environment.ts b/src/environments/environment.ts new file mode 100644 index 0000000..6b4a068 --- /dev/null +++ b/src/environments/environment.ts @@ -0,0 +1,10 @@ +export const environment = { + production: false, + apiUrl: 'https://localhost:7204', + frontendUrl: 'https://localhost:4200', + auth: { + tokenKey: 'auth_token', + userKey: 'user_data', + externalCookieKey: 'ExternalCookie' + } +}; From 83ef0953fc672ebc09b2abf92597bc380136eb9e Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:13:46 +0300 Subject: [PATCH 03/21] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e178245..0fc8eb6 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,4 @@ testem.log Thumbs.db # Environment files -/environments +src/environments/environment.ts From c92bdccdfc8fa7a0674272c0df3f645dbd2b414a Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:17:41 +0300 Subject: [PATCH 04/21] Remove environment.ts --- src/environments/environment.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/environments/environment.ts diff --git a/src/environments/environment.ts b/src/environments/environment.ts deleted file mode 100644 index 6b4a068..0000000 --- a/src/environments/environment.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const environment = { - production: false, - apiUrl: 'https://localhost:7204', - frontendUrl: 'https://localhost:4200', - auth: { - tokenKey: 'auth_token', - userKey: 'user_data', - externalCookieKey: 'ExternalCookie' - } -}; From 87407e9f173e3a5dd4253e014e2cda99cab2421c Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:20:10 +0300 Subject: [PATCH 05/21] Add authentication models --- src/app/models/auth.models.ts | 74 +++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/app/models/auth.models.ts diff --git a/src/app/models/auth.models.ts b/src/app/models/auth.models.ts new file mode 100644 index 0000000..cb6f9a5 --- /dev/null +++ b/src/app/models/auth.models.ts @@ -0,0 +1,74 @@ +// Request Models +export interface LoginRequest { + userName: string; + password: string; +} + +export interface RegisterRequest { + email: string; + userName: string; + fullName: string; + password: string; + phoneNumber?: string; +} + +export interface ResetPasswordRequest { + email: string; + token: string; + password: string; +} + +export interface ChangePasswordRequest { + currentPassword: string; + newPassword: string; + confirmNewPassword: string; +} + +export interface AssignRoleRequest { + userId: string; + role: string; +} + +// Response Models +export interface ServiceResponse { + success: boolean; + message: string; + data?: T; + errors?: string[]; +} + +export interface LoginResponse { + token: string; + returnUrl?: string; +} + +export interface UserDto { + id: string; + email: string; + userName: string; + fullName: string; + phoneNumber: string; + emailConfirmed: boolean; + roles: string[]; + createdAt: Date; + updatedAt?: Date; + deletedAt?: Date; +} + +export interface PaginatedResponse { + items: T[]; + totalCount: number; + pageNumber: number; + pageSize: number; + totalPages: number; + hasPrevious: boolean; + hasNext: boolean; +} + +export interface PaginationParameters { + pageNumber: number; + pageSize: number; + searchTerm?: string; + sortBy?: string; + sortDescending?: boolean; +} From 37ec76cab1e403860fc86db27a4e773bbff01a67 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:21:18 +0300 Subject: [PATCH 06/21] Create authentication interceptor --- src/app/interceptors/auth.interceptor.ts | 56 ++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/app/interceptors/auth.interceptor.ts diff --git a/src/app/interceptors/auth.interceptor.ts b/src/app/interceptors/auth.interceptor.ts new file mode 100644 index 0000000..ec6d323 --- /dev/null +++ b/src/app/interceptors/auth.interceptor.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor, + HttpErrorResponse +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { AuthService } from '../services/auth.service'; +import { Router } from '@angular/router'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + constructor( + private authService: AuthService, + private router: Router + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + // Get the auth token from the service + const token = this.authService.getToken(); + + // Clone the request and add the authorization header + let authReq = request; + if (token) { + authReq = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + + // Handle the request and catch any errors + return next.handle(authReq).pipe( + catchError((error: HttpErrorResponse) => { + // If we get a 401 Unauthorized, redirect to login + if (error.status === 401) { + this.authService.logout(); + this.router.navigate(['/login'], { + queryParams: { returnUrl: this.router.url } + }); + } + + // Handle 403 Forbidden + if (error.status === 403) { + // Could show a toast notification here + console.error('You do not have permission to perform this action.'); + } + + return throwError(() => error); + }) + ); + } +} From aeb416a6fcc1c9efe14e9835c5ebff6598f07e13 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:22:54 +0300 Subject: [PATCH 07/21] Create authentication service --- src/app/services/auth.service.ts | 385 +++++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 src/app/services/auth.service.ts diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..f04ac44 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,385 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable, BehaviorSubject, throwError } from 'rxjs'; +import { map, catchError, tap } from 'rxjs/operators'; +import { Router } from '@angular/router'; +import { environment } from '../../environments/environment'; + +// Import models +import { + LoginRequest, + RegisterRequest, + ResetPasswordRequest, + ChangePasswordRequest, + ServiceResponse, + LoginResponse, + UserDto +} from '../models/auth.models'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + private apiUrl = environment.apiUrl; + private tokenKey = environment.auth.tokenKey; + private userKey = environment.auth.userKey; + + // BehaviorSubject to track authentication state + private currentUserSubject = new BehaviorSubject(null); + public currentUser$ = this.currentUserSubject.asObservable(); + + private isAuthenticatedSubject = new BehaviorSubject(false); + public isAuthenticated$ = this.isAuthenticatedSubject.asObservable(); + + constructor( + private http: HttpClient, + private router: Router + ) { + // Check if user is already logged in on app initialization + this.loadStoredUser(); + } + + // ============================== + // Authentication Methods + // ============================== + + /** + * Login user with username and password + */ + login(credentials: LoginRequest): Observable> { + return this.http.post>( + `${this.apiUrl}/User/login`, + credentials + ).pipe( + tap(response => { + if (response.success && response.data) { + this.handleSuccessfulLogin(response.data.token); + } + }), + catchError(this.handleError) + ); + } + + /** + * Register a new user + */ + register(userData: RegisterRequest): Observable> { + return this.http.post>( + `${this.apiUrl}/User/register`, + userData + ).pipe( + catchError(this.handleError) + ); + } + + /** + * Logout user + */ + logout(): void { + localStorage.removeItem(this.tokenKey); + localStorage.removeItem(this.userKey); + this.currentUserSubject.next(null); + this.isAuthenticatedSubject.next(false); + this.router.navigate(['/login']); + } + + /** + * Confirm email with token + */ + confirmEmail(email: string, token: string): Observable> { + return this.http.get>( + `${this.apiUrl}/User/confirm-email`, + { + params: { + email: email, + encodedToken: token + } + } + ).pipe( + catchError(this.handleError) + ); + } + + /** + * Resend email confirmation + */ + resendConfirmation(email: string): Observable> { + return this.http.post>( + `${this.apiUrl}/User/resend-confirmation`, + email + ).pipe( + catchError(this.handleError) + ); + } + + /** + * Request password reset + */ + forgotPassword(email: string): Observable> { + return this.http.post>( + `${this.apiUrl}/User/forgot-password`, + email + ).pipe( + catchError(this.handleError) + ); + } + + /** + * Reset password with token + */ + resetPassword(resetData: ResetPasswordRequest): Observable> { + return this.http.post>( + `${this.apiUrl}/User/reset-password`, + resetData + ).pipe( + catchError(this.handleError) + ); + } + + /** + * Change password (authenticated user) + */ + changePassword(userId: string, changeData: ChangePasswordRequest): Observable> { + return this.http.put>( + `${this.apiUrl}/User/${userId}/change-password`, + changeData + ).pipe( + catchError(this.handleError) + ); + } + + // ============================== + // User Management Methods + // ============================== + + /** + * Get current user profile + */ + getCurrentUser(): Observable> { + const userId = this.getUserId(); + if (!userId) { + return throwError(() => new Error('User not authenticated')); + } + + return this.http.get>( + `${this.apiUrl}/User/${userId}` + ).pipe( + tap(response => { + if (response.success && response.data) { + this.currentUserSubject.next(response.data); + localStorage.setItem(this.userKey, JSON.stringify(response.data)); + } + }), + catchError(this.handleError) + ); + } + + /** + * Get user by ID + */ + getUserById(userId: string): Observable> { + return this.http.get>( + `${this.apiUrl}/User/${userId}` + ).pipe( + catchError(this.handleError) + ); + } + + // ============================== + // External Login Methods + // ============================== + + /** + * Initiate external login with provider + */ + externalLogin(provider: string, returnUrl: string = '/'): void { + const redirectUrl = `${this.apiUrl}/ExternalLogin/external-login`; + window.location.href = `${redirectUrl}?provider=${provider}&returnUrl=${returnUrl}`; + } + + /** + * Handle external login callback + */ + handleExternalLoginCallback(): Observable> { + // Parse the return URL from query parameters + const urlParams = new URLSearchParams(window.location.search); + const returnUrl = urlParams.get('returnUrl') || '/'; + const remoteError = urlParams.get('remoteError'); + + if (remoteError) { + return throwError(() => new Error(`External login error: ${remoteError}`)); + } + + // The backend will handle the external login and return the token + // The token is returned in the response data + return this.http.get>( + `${this.apiUrl}/ExternalLogin/external-login-callback`, + { + params: { + returnUrl: returnUrl + } + } + ).pipe( + tap(response => { + if (response.success && response.data) { + this.handleSuccessfulLogin(response.data.token); + // Redirect to return URL after successful login + this.router.navigateByUrl(returnUrl); + } + }), + catchError(this.handleError) + ); + } + + // ============================== + // Token and User Management + // ============================== + + /** + * Get stored JWT token + */ + getToken(): string | null { + return localStorage.getItem(this.tokenKey); + } + + /** + * Get stored user data + */ + getUser(): UserDto | null { + const userData = localStorage.getItem(this.userKey); + if (userData) { + try { + return JSON.parse(userData); + } catch { + return null; + } + } + return null; + } + + /** + * Get current user ID from stored user + */ + getUserId(): string | null { + const user = this.getUser(); + return user ? user.id : null; + } + + /** + * Check if user is authenticated + */ + isAuthenticated(): boolean { + return !!this.getToken() && !!this.getUser(); + } + + /** + * Check if user has specific role + */ + hasRole(role: string): boolean { + const user = this.getUser(); + return user ? user.roles.includes(role) : false; + } + + /** + * Check if user is Admin + */ + isAdmin(): boolean { + return this.hasRole('Admin'); + } + + /** + * Check if user is SuperAdmin + */ + isSuperAdmin(): boolean { + return this.hasRole('SuperAdmin'); + } + + // ============================== + // Private Helper Methods + // ============================== + + private handleSuccessfulLogin(token: string): void { + // Store token + localStorage.setItem(this.tokenKey, token); + + // Load user data from token or fetch from API + this.loadUserData(); + + // Update authentication state + this.isAuthenticatedSubject.next(true); + } + + private loadStoredUser(): void { + const user = this.getUser(); + const token = this.getToken(); + + if (user && token) { + this.currentUserSubject.next(user); + this.isAuthenticatedSubject.next(true); + } else { + this.currentUserSubject.next(null); + this.isAuthenticatedSubject.next(false); + } + } + + private loadUserData(): void { + // If we have a token but no user data, fetch it + if (this.getToken() && !this.getUser()) { + this.getCurrentUser().subscribe({ + next: (response) => { + if (response.success && response.data) { + this.currentUserSubject.next(response.data); + localStorage.setItem(this.userKey, JSON.stringify(response.data)); + } + }, + error: (error) => { + console.error('Failed to load user data:', error); + // If we can't load user data, logout + this.logout(); + } + }); + } else { + // Use stored user data + const user = this.getUser(); + if (user) { + this.currentUserSubject.next(user); + } + } + } + + private handleError(error: any): Observable { + let errorMessage = 'An error occurred'; + + if (error.error instanceof ErrorEvent) { + // Client-side error + errorMessage = error.error.message; + } else { + // Server-side error + if (error.error && error.error.message) { + errorMessage = error.error.message; + } else if (error.status) { + switch (error.status) { + case 400: + errorMessage = 'Bad request. Please check your input.'; + break; + case 401: + errorMessage = 'Unauthorized. Please login again.'; + break; + case 403: + errorMessage = 'You do not have permission to perform this action.'; + break; + case 404: + errorMessage = 'Resource not found.'; + break; + case 500: + errorMessage = 'Server error. Please try again later.'; + break; + default: + errorMessage = `Error ${error.status}: ${error.statusText}`; + } + } + } + + return throwError(() => new Error(errorMessage)); + } +} From 99aca1bec3adf4268c399af34e663776b8418c25 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:24:40 +0300 Subject: [PATCH 08/21] Create rule guard --- src/app/guards/role.guard.ts | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/app/guards/role.guard.ts diff --git a/src/app/guards/role.guard.ts b/src/app/guards/role.guard.ts new file mode 100644 index 0000000..a3b491e --- /dev/null +++ b/src/app/guards/role.guard.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + ActivatedRouteSnapshot, + RouterStateSnapshot, + Router, + UrlTree +} from '@angular/router'; +import { Observable } from 'rxjs'; +import { map, take } from 'rxjs/operators'; +import { AuthService } from '../services/auth.service'; + +@Injectable({ + providedIn: 'root' +}) +export class RoleGuard implements CanActivate { + constructor( + private authService: AuthService, + private router: Router + ) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Observable | Promise | boolean | UrlTree { + const requiredRoles = route.data['roles'] as string[]; + + if (!requiredRoles || requiredRoles.length === 0) { + return true; + } + + return this.authService.currentUser$.pipe( + take(1), + map(user => { + if (!user) { + return this.router.createUrlTree(['/login'], { + queryParams: { returnUrl: state.url } + }); + } + + const hasRequiredRole = requiredRoles.some(role => user.roles.includes(role)); + + if (hasRequiredRole) { + return true; + } + + // User doesn't have required role - redirect to unauthorized page or home + return this.router.createUrlTree(['/unauthorized']); + }) + ); + } +} From f667f13e39964b6a3ad2ce591a132828e3c7caf3 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 00:48:09 +0300 Subject: [PATCH 09/21] Create authentication guard --- src/app/guards/auth.guard.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/app/guards/auth.guard.ts diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..15a2d9e --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { CanActivate, Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; +import { map, take } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard implements CanActivate { + constructor(private authService: AuthService, private router: Router) {} + + canActivate() { + return this.authService.isAuthenticated$.pipe( + take(1), + map(isAuthenticated => { + if (isAuthenticated) { + return true; + } + return this.router.createUrlTree(['/login']); + }) + ); + } +} From c98bed4b3a836945969104e10e1bfd7fea08ea27 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 01:13:38 +0300 Subject: [PATCH 10/21] Update the app.config.ts to use functional interceptor --- src/app/app.config.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 8196731..7062b4a 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -8,8 +8,10 @@ import { withRouterConfig, withViewTransitions } from '@angular/router'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { IconSetService } from '@coreui/icons-angular'; import { routes } from './app.routes'; +import { authInterceptor } from './interceptors/auth.interceptor'; export const appConfig: ApplicationConfig = { providers: [ @@ -26,7 +28,11 @@ export const appConfig: ApplicationConfig = { withHashLocation() ), IconSetService, - provideAnimationsAsync() + provideAnimationsAsync(), + + // HTTP Client with functional interceptor + provideHttpClient( + withInterceptors([authInterceptor]) + ) ] }; - From 8adaef701bf22405967c32563c87f23db6bbc7a4 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 01:14:28 +0300 Subject: [PATCH 11/21] Update the authentication interceptor to be functional --- src/app/interceptors/auth.interceptor.ts | 86 ++++++++++-------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/src/app/interceptors/auth.interceptor.ts b/src/app/interceptors/auth.interceptor.ts index ec6d323..4404112 100644 --- a/src/app/interceptors/auth.interceptor.ts +++ b/src/app/interceptors/auth.interceptor.ts @@ -1,56 +1,44 @@ -import { Injectable } from '@angular/core'; -import { - HttpRequest, - HttpHandler, - HttpEvent, - HttpInterceptor, - HttpErrorResponse -} from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; -import { catchError } from 'rxjs/operators'; -import { AuthService } from '../services/auth.service'; +import { HttpInterceptorFn } from '@angular/common/http'; +import { inject } from '@angular/core'; import { Router } from '@angular/router'; +import { AuthService } from '../services/auth.service'; +import { catchError } from 'rxjs/operators'; +import { throwError } from 'rxjs'; -@Injectable() -export class AuthInterceptor implements HttpInterceptor { - constructor( - private authService: AuthService, - private router: Router - ) {} +export const authInterceptor: HttpInterceptorFn = (req, next) => { + const authService = inject(AuthService); + const router = inject(Router); - intercept(request: HttpRequest, next: HttpHandler): Observable> { - // Get the auth token from the service - const token = this.authService.getToken(); + // Get the auth token from the service + const token = authService.getToken(); - // Clone the request and add the authorization header - let authReq = request; - if (token) { - authReq = request.clone({ - setHeaders: { - Authorization: `Bearer ${token}` - } - }); - } + // Clone the request and add the authorization header + let authReq = req; + if (token) { + authReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } - // Handle the request and catch any errors - return next.handle(authReq).pipe( - catchError((error: HttpErrorResponse) => { - // If we get a 401 Unauthorized, redirect to login - if (error.status === 401) { - this.authService.logout(); - this.router.navigate(['/login'], { - queryParams: { returnUrl: this.router.url } - }); - } + // Handle the request and catch any errors + return next(authReq).pipe( + catchError((error) => { + // If we get a 401 Unauthorized, redirect to login + if (error.status === 401) { + authService.logout(); + router.navigate(['/login'], { + queryParams: { returnUrl: router.url } + }); + } - // Handle 403 Forbidden - if (error.status === 403) { - // Could show a toast notification here - console.error('You do not have permission to perform this action.'); - } + // Handle 403 Forbidden + if (error.status === 403) { + console.error('You do not have permission to perform this action.'); + } - return throwError(() => error); - }) - ); - } -} + return throwError(() => error); + }) + ); +}; From 2161b54a385811d2b6d8a0f036c26a37d324c9ea Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 01:14:58 +0300 Subject: [PATCH 12/21] Update the auth guard --- src/app/guards/auth.guard.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts index 15a2d9e..5904ca9 100644 --- a/src/app/guards/auth.guard.ts +++ b/src/app/guards/auth.guard.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { CanActivate, Router } from '@angular/router'; +import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } from '../services/auth.service'; import { map, take } from 'rxjs/operators'; @@ -9,14 +9,18 @@ import { map, take } from 'rxjs/operators'; export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} - canActivate() { + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { return this.authService.isAuthenticated$.pipe( take(1), map(isAuthenticated => { if (isAuthenticated) { return true; } - return this.router.createUrlTree(['/login']); + + // Store the attempted URL for redirecting after login + return this.router.createUrlTree(['/login'], { + queryParams: { returnUrl: state.url } + }); }) ); } From e3c163a50f6bb669abc385f229d74db700a3f63c Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 01:22:38 +0300 Subject: [PATCH 13/21] Update app.routs.ts --- src/app/app.routes.ts | 47 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 9bb43fe..99d10f9 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,4 +1,6 @@ import { Routes } from '@angular/router'; +import { AuthGuard } from './guards/auth.guard'; +import { RoleGuard } from './guards/role.guard'; export const routes: Routes = [ { @@ -12,6 +14,7 @@ export const routes: Routes = [ data: { title: 'Home' }, + canActivate: [AuthGuard], // Protect all child routes children: [ { path: 'dashboard', @@ -55,6 +58,20 @@ export const routes: Routes = [ } ] }, + { + path: 'login', + loadComponent: () => import('./views/pages/login/login.component').then(m => m.LoginComponent), + data: { + title: 'Login Page' + } + }, + { + path: 'register', + loadComponent: () => import('./views/pages/register/register.component').then(m => m.RegisterComponent), + data: { + title: 'Register Page' + } + }, { path: '404', loadComponent: () => import('./views/pages/page404/page404.component').then(m => m.Page404Component), @@ -69,19 +86,37 @@ export const routes: Routes = [ title: 'Page 500' } }, + // ============================== + // ADMIN & SUPER ADMIN ROUTES (Commented - Will be implemented later) + // ============================== + + // Protected Admin routes with role guard + // Uncomment when admin component is created + /* { - path: 'login', - loadComponent: () => import('./views/pages/login/login.component').then(m => m.LoginComponent), + path: 'admin', + loadComponent: () => import('./views/admin/admin.component').then(m => m.AdminComponent), + canActivate: [AuthGuard, RoleGuard], data: { - title: 'Login Page' + roles: ['Admin', 'SuperAdmin'], + title: 'Admin' } }, + */ + + // Protected SuperAdmin routes with role guard + // Uncomment when super-admin component is created + /* { - path: 'register', - loadComponent: () => import('./views/pages/register/register.component').then(m => m.RegisterComponent), + path: 'super-admin', + loadComponent: () => import('./views/super-admin/super-admin.component').then(m => m.SuperAdminComponent), + canActivate: [AuthGuard, RoleGuard], data: { - title: 'Register Page' + roles: ['SuperAdmin'], + title: 'Super Admin' } }, + */ + { path: '**', redirectTo: 'dashboard' } ]; From d8d713ff029bc9057e60bdd9b985baf41a9829c4 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 03:18:48 +0300 Subject: [PATCH 14/21] Update login component --- .../views/pages/login/login.component.html | 61 ++++++++++-- src/app/views/pages/login/login.component.ts | 94 ++++++++++++++++++- 2 files changed, 141 insertions(+), 14 deletions(-) diff --git a/src/app/views/pages/login/login.component.html b/src/app/views/pages/login/login.component.html index a1cf9b4..7c285ff 100644 --- a/src/app/views/pages/login/login.component.html +++ b/src/app/views/pages/login/login.component.html @@ -5,34 +5,69 @@ -
+

Login

Sign In to your account

+ + + + {{ errorMessage }} + + + - + +
+ Username is required +
+ + +
+ Password is required + Password must be at least 6 characters +
+ - - @@ -45,10 +80,16 @@

Login

Sign up

- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod - tempor incididunt ut labore et dolore magna aliqua. + Create your account and start using our platform today. + Join our community and access all features.

-
diff --git a/src/app/views/pages/login/login.component.ts b/src/app/views/pages/login/login.component.ts index e9ab25b..03aa765 100644 --- a/src/app/views/pages/login/login.component.ts +++ b/src/app/views/pages/login/login.component.ts @@ -1,4 +1,6 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Router, RouterLink, ActivatedRoute } from '@angular/router'; import { IconDirective } from '@coreui/icons-angular'; import { ButtonDirective, @@ -11,12 +13,96 @@ import { FormDirective, InputGroupComponent, InputGroupTextDirective, - RowComponent + RowComponent, + AlertComponent, + SpinnerComponent } from '@coreui/angular'; +import { AuthService } from '../../../services/auth.service'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'app-login', templateUrl: './login.component.html', - imports: [ContainerComponent, RowComponent, ColComponent, CardGroupComponent, CardComponent, CardBodyComponent, FormDirective, InputGroupComponent, InputGroupTextDirective, IconDirective, FormControlDirective, ButtonDirective] + imports: [ + CommonModule, + ReactiveFormsModule, + ContainerComponent, + RowComponent, + ColComponent, + CardGroupComponent, + CardComponent, + CardBodyComponent, + FormDirective, + InputGroupComponent, + InputGroupTextDirective, + IconDirective, + FormControlDirective, + ButtonDirective, + RouterLink, + AlertComponent, + SpinnerComponent + ] }) -export class LoginComponent {} +export class LoginComponent implements OnInit { + loginForm: FormGroup; + isLoading = false; + errorMessage: string | null = null; + returnUrl: string = '/dashboard'; + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private router: Router, + private route: ActivatedRoute + ) { + this.loginForm = this.fb.group({ + userName: ['', [Validators.required]], + password: ['', [Validators.required, Validators.minLength(6)]] + }); + } + + ngOnInit(): void { + // If user is already logged in, redirect to dashboard + if (this.authService.isAuthenticated()) { + this.router.navigate(['/dashboard']); + return; + } + + // Get return URL from query params + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard'; + } + + onSubmit(): void { + if (this.loginForm.invalid) { + // Mark all fields as touched to trigger validation messages + Object.keys(this.loginForm.controls).forEach(key => { + this.loginForm.get(key)?.markAsTouched(); + }); + return; + } + + this.isLoading = true; + this.errorMessage = null; + + this.authService.login(this.loginForm.value).subscribe({ + next: (response) => { + this.isLoading = false; + if (response.success) { + // Redirect to the return URL or dashboard + this.router.navigateByUrl(this.returnUrl); + } else { + this.errorMessage = response.message || 'Login failed. Please try again.'; + } + }, + error: (error) => { + this.isLoading = false; + this.errorMessage = error.message || 'An error occurred during login.'; + console.error('Login error:', error); + } + }); + } + + // Convenience getters for form controls + get userName() { return this.loginForm.get('userName'); } + get password() { return this.loginForm.get('password'); } +} From 8a5f5c1149da93d79cbf5519e367db307de55f41 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 03:21:23 +0300 Subject: [PATCH 15/21] Update register page and pages routs --- .../pages/register/register.component.html | 127 +++++++++++++++++- .../pages/register/register.component.ts | 107 ++++++++++++++- src/app/views/pages/routes.ts | 35 ++++- 3 files changed, 259 insertions(+), 10 deletions(-) diff --git a/src/app/views/pages/register/register.component.html b/src/app/views/pages/register/register.component.html index 68bffd1..d7d820a 100644 --- a/src/app/views/pages/register/register.component.html +++ b/src/app/views/pages/register/register.component.html @@ -4,33 +4,148 @@ - +

Register

Create your account

+ + + + {{ successMessage }} + + + + + {{ errorMessage }} + + + - + +
+ Full name is required + Full name must be at least 2 characters +
+ + + + + + + + +
+ Username is required + Username must be at least 3 characters +
+ + @ - + +
+ Email is required + Please enter a valid email address +
+ + + + + + + + + + - + +
+ Password is required + Password must be at least 6 characters +
+ + - + +
+ Passwords do not match +
+
- + +
+ +
+

+ Already have an account? + Login +

diff --git a/src/app/views/pages/register/register.component.ts b/src/app/views/pages/register/register.component.ts index c19b544..50661d7 100644 --- a/src/app/views/pages/register/register.component.ts +++ b/src/app/views/pages/register/register.component.ts @@ -1,4 +1,6 @@ import { Component } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, AbstractControl, ValidationErrors } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; import { IconDirective } from '@coreui/icons-angular'; import { ButtonDirective, @@ -10,12 +12,111 @@ import { FormDirective, InputGroupComponent, InputGroupTextDirective, - RowComponent + RowComponent, + AlertComponent, + SpinnerComponent } from '@coreui/angular'; +import { AuthService } from '../../../services/auth.service'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'app-register', templateUrl: './register.component.html', - imports: [ContainerComponent, RowComponent, ColComponent, CardComponent, CardBodyComponent, FormDirective, InputGroupComponent, InputGroupTextDirective, IconDirective, FormControlDirective, ButtonDirective] + imports: [ + CommonModule, + ReactiveFormsModule, + ContainerComponent, + RowComponent, + ColComponent, + CardComponent, + CardBodyComponent, + FormDirective, + InputGroupComponent, + InputGroupTextDirective, + IconDirective, + FormControlDirective, + ButtonDirective, + RouterLink, + AlertComponent, + SpinnerComponent + ] }) -export class RegisterComponent {} +export class RegisterComponent { + registerForm: FormGroup; + isLoading = false; + errorMessage: string | null = null; + successMessage: string | null = null; + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private router: Router + ) { + this.registerForm = this.fb.group({ + fullName: ['', [Validators.required, Validators.minLength(2)]], + userName: ['', [Validators.required, Validators.minLength(3)]], + email: ['', [Validators.required, Validators.email]], + phoneNumber: ['', []], + password: ['', [Validators.required, Validators.minLength(6)]], + confirmPassword: ['', [Validators.required]] + }, { + validators: this.passwordMatchValidator + }); + } + + // Custom validator to check if passwords match + passwordMatchValidator(control: AbstractControl): ValidationErrors | null { + const password = control.get('password'); + const confirmPassword = control.get('confirmPassword'); + + if (!password || !confirmPassword) { + return null; + } + + return password.value === confirmPassword.value ? null : { passwordMismatch: true }; + } + + onSubmit(): void { + if (this.registerForm.invalid) { + Object.keys(this.registerForm.controls).forEach(key => { + this.registerForm.get(key)?.markAsTouched(); + }); + return; + } + + this.isLoading = true; + this.errorMessage = null; + this.successMessage = null; + + // Remove confirmPassword from the request payload + const { confirmPassword, ...registrationData } = this.registerForm.value; + + this.authService.register(registrationData).subscribe({ + next: (response) => { + this.isLoading = false; + if (response.success) { + this.successMessage = 'Registration successful! Please check your email to confirm your account.'; + // Redirect to login after 3 seconds + setTimeout(() => { + this.router.navigate(['/login']); + }, 3000); + } else { + this.errorMessage = response.message || 'Registration failed. Please try again.'; + } + }, + error: (error) => { + this.isLoading = false; + this.errorMessage = error.message || 'An error occurred during registration.'; + console.error('Registration error:', error); + } + }); + } + + // Convenience getters for form controls + get fullName() { return this.registerForm.get('fullName'); } + get userName() { return this.registerForm.get('userName'); } + get email() { return this.registerForm.get('email'); } + get phoneNumber() { return this.registerForm.get('phoneNumber'); } + get password() { return this.registerForm.get('password'); } + get confirmPassword() { return this.registerForm.get('confirmPassword'); } +} diff --git a/src/app/views/pages/routes.ts b/src/app/views/pages/routes.ts index 410f516..e3fcf6e 100644 --- a/src/app/views/pages/routes.ts +++ b/src/app/views/pages/routes.ts @@ -28,5 +28,38 @@ export const routes: Routes = [ data: { title: 'Register Page' } - } + }, + // ============================== + // FORGOT & RESET PASSWORD ROUTES (Commented - Will be implemented later) + // ============================== + // Uncomment when forgot-password component is created + /* + { + path: 'forgot-password', + loadComponent: () => import('./forgot-password/forgot-password.component').then(m => m.ForgotPasswordComponent), + data: { + title: 'Forgot Password' + } + }, + { + path: 'reset-password', + loadComponent: () => import('./reset-password/reset-password.component').then(m => m.ResetPasswordComponent), + data: { + title: 'Reset Password' + } + }, + */ + // ============================== + // EXTERNAL LOGIN CALLBACK ROUTE (Commented - Will be implemented later) + // ============================== + // Uncomment when external-login-callback component is created + /* + { + path: 'external-login-callback', + loadComponent: () => import('./external-login-callback/external-login-callback.component').then(m => m.ExternalLoginCallbackComponent), + data: { + title: 'External Login Callback' + } + }, + */ ]; From 3e6c2267cafbdf4b474d0b98ca2be9f521f0a90e Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 03:34:19 +0300 Subject: [PATCH 16/21] Update login & register components to use Bootstrap's built-in spinner & [ngClass] instead of invalid --- .../views/pages/login/login.component.html | 12 ++++--- src/app/views/pages/login/login.component.ts | 6 ++-- .../pages/register/register.component.html | 35 ++++++++++++------- .../pages/register/register.component.ts | 10 +++--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/app/views/pages/login/login.component.html b/src/app/views/pages/login/login.component.html index 7c285ff..41622c4 100644 --- a/src/app/views/pages/login/login.component.html +++ b/src/app/views/pages/login/login.component.html @@ -25,8 +25,10 @@

Login

formControlName="userName" placeholder="Username or Email" autoComplete="username" - [valid]="userName?.valid" - [invalid]="userName?.invalid && userName?.touched" + [ngClass]="{ + 'is-invalid': userName?.invalid && userName?.touched, + 'is-valid': userName?.valid && userName?.touched + }" />
@@ -44,8 +46,10 @@

Login

formControlName="password" placeholder="Password" autoComplete="current-password" - [valid]="password?.valid" - [invalid]="password?.invalid && password?.touched" + [ngClass]="{ + 'is-invalid': password?.invalid && password?.touched, + 'is-valid': password?.valid && password?.touched + }" />
diff --git a/src/app/views/pages/login/login.component.ts b/src/app/views/pages/login/login.component.ts index 03aa765..7b485b2 100644 --- a/src/app/views/pages/login/login.component.ts +++ b/src/app/views/pages/login/login.component.ts @@ -14,8 +14,7 @@ import { InputGroupComponent, InputGroupTextDirective, RowComponent, - AlertComponent, - SpinnerComponent + AlertComponent } from '@coreui/angular'; import { AuthService } from '../../../services/auth.service'; import { CommonModule } from '@angular/common'; @@ -39,8 +38,7 @@ import { CommonModule } from '@angular/common'; FormControlDirective, ButtonDirective, RouterLink, - AlertComponent, - SpinnerComponent + AlertComponent ] }) export class LoginComponent implements OnInit { diff --git a/src/app/views/pages/register/register.component.html b/src/app/views/pages/register/register.component.html index d7d820a..534a892 100644 --- a/src/app/views/pages/register/register.component.html +++ b/src/app/views/pages/register/register.component.html @@ -29,8 +29,10 @@

Register

formControlName="fullName" placeholder="Full Name" autoComplete="name" - [valid]="fullName?.valid" - [invalid]="fullName?.invalid && fullName?.touched" + [ngClass]="{ + 'is-invalid': fullName?.invalid && fullName?.touched, + 'is-valid': fullName?.valid && fullName?.touched + }" />
@@ -49,8 +51,10 @@

Register

formControlName="userName" placeholder="Username" autoComplete="username" - [valid]="userName?.valid" - [invalid]="userName?.invalid && userName?.touched" + [ngClass]="{ + 'is-invalid': userName?.invalid && userName?.touched, + 'is-valid': userName?.valid && userName?.touched + }" />
@@ -67,8 +71,10 @@

Register

formControlName="email" placeholder="Email" autoComplete="email" - [valid]="email?.valid" - [invalid]="email?.invalid && email?.touched" + [ngClass]="{ + 'is-invalid': email?.invalid && email?.touched, + 'is-valid': email?.valid && email?.touched + }" />
@@ -101,8 +107,10 @@

Register

formControlName="password" placeholder="Password" autoComplete="new-password" - [valid]="password?.valid" - [invalid]="password?.invalid && password?.touched" + [ngClass]="{ + 'is-invalid': password?.invalid && password?.touched, + 'is-valid': password?.valid && password?.touched + }" />
@@ -121,12 +129,15 @@

Register

formControlName="confirmPassword" placeholder="Confirm Password" autoComplete="new-password" - [valid]="confirmPassword?.valid" - [invalid]="confirmPassword?.invalid && confirmPassword?.touched" + [ngClass]="{ + 'is-invalid': (confirmPassword?.invalid && confirmPassword?.touched) || registerForm.errors?.['passwordMismatch'], + 'is-valid': confirmPassword?.valid && confirmPassword?.touched + }" /> -
- Passwords do not match +
+ Please confirm your password + Passwords do not match
diff --git a/src/app/views/pages/register/register.component.ts b/src/app/views/pages/register/register.component.ts index 50661d7..7cb3321 100644 --- a/src/app/views/pages/register/register.component.ts +++ b/src/app/views/pages/register/register.component.ts @@ -13,8 +13,7 @@ import { InputGroupComponent, InputGroupTextDirective, RowComponent, - AlertComponent, - SpinnerComponent + AlertComponent } from '@coreui/angular'; import { AuthService } from '../../../services/auth.service'; import { CommonModule } from '@angular/common'; @@ -37,8 +36,7 @@ import { CommonModule } from '@angular/common'; FormControlDirective, ButtonDirective, RouterLink, - AlertComponent, - SpinnerComponent + AlertComponent ] }) export class RegisterComponent { @@ -102,6 +100,10 @@ export class RegisterComponent { }, 3000); } else { this.errorMessage = response.message || 'Registration failed. Please try again.'; + // Show specific error messages from server + if (response.errors && response.errors.length > 0) { + this.errorMessage = response.errors.join(', '); + } } }, error: (error) => { From d5db44a9cdb57500eb2fe143aab0b1f15951cfc5 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sun, 2 Aug 2026 03:42:40 +0300 Subject: [PATCH 17/21] Register Icons in app.componenets.ts --- src/app/app.component.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index de1d46d..8e6b558 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,12 +6,14 @@ import { delay, filter, map, tap } from 'rxjs/operators'; import { ColorModeService } from '@coreui/angular'; import { IconSetService } from '@coreui/icons-angular'; +import { freeSet, brandSet, flagSet } from '@coreui/icons'; // Import all icon sets import { iconSubset } from './icons/icon-subset'; @Component({ selector: 'app-root', template: '', - imports: [RouterOutlet] + imports: [RouterOutlet], + providers: [IconSetService] // Add IconSetService as a provider }) export class AppComponent implements OnInit { title = 'CoreUI Angular Admin Template'; @@ -26,14 +28,21 @@ export class AppComponent implements OnInit { constructor() { this.#titleService.setTitle(this.title); - // iconSet singleton - this.#iconSetService.icons = { ...iconSubset }; + + // Register ALL icon sets globally + // This makes ALL icons available everywhere in the app + this.#iconSetService.icons = { + ...iconSubset, // Your custom icons + ...freeSet, // cil icons (including cilPhone, cilUser, cilLockLocked) + ...brandSet, // cib icons (Google, Facebook, Twitter, etc.) + ...flagSet // cif icons (Country flags) + }; + this.#colorModeService.localStorageItemName.set('coreui-free-angular-admin-template-theme-default'); this.#colorModeService.eventName.set('ColorSchemeChange'); } ngOnInit(): void { - this.#router.events.pipe( takeUntilDestroyed(this.#destroyRef) ).subscribe((evt) => { From 323d702ee2e7ad29a1c0eb73eb6354d886876400 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sat, 8 Aug 2026 16:01:22 +0300 Subject: [PATCH 18/21] Update Auth service to get the user from login endpoint --- src/app/services/auth.service.ts | 74 +++++++++++--------------------- 1 file changed, 25 insertions(+), 49 deletions(-) diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index f04ac44..c1d4729 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -1,11 +1,10 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Observable, BehaviorSubject, throwError } from 'rxjs'; -import { map, catchError, tap } from 'rxjs/operators'; +import { catchError, tap } from 'rxjs/operators'; import { Router } from '@angular/router'; import { environment } from '../../environments/environment'; -// Import models import { LoginRequest, RegisterRequest, @@ -24,7 +23,6 @@ export class AuthService { private tokenKey = environment.auth.tokenKey; private userKey = environment.auth.userKey; - // BehaviorSubject to track authentication state private currentUserSubject = new BehaviorSubject(null); public currentUser$ = this.currentUserSubject.asObservable(); @@ -35,7 +33,6 @@ export class AuthService { private http: HttpClient, private router: Router ) { - // Check if user is already logged in on app initialization this.loadStoredUser(); } @@ -53,7 +50,17 @@ export class AuthService { ).pipe( tap(response => { if (response.success && response.data) { - this.handleSuccessfulLogin(response.data.token); + // Store the token + localStorage.setItem(this.tokenKey, response.data.token); + + // Store the user data if available + if (response.data.user) { + this.currentUserSubject.next(response.data.user); + localStorage.setItem(this.userKey, JSON.stringify(response.data.user)); + console.log('User data stored from login response:', response.data.user); + } + + this.isAuthenticatedSubject.next(true); } }), catchError(this.handleError) @@ -201,7 +208,6 @@ export class AuthService { * Handle external login callback */ handleExternalLoginCallback(): Observable> { - // Parse the return URL from query parameters const urlParams = new URLSearchParams(window.location.search); const returnUrl = urlParams.get('returnUrl') || '/'; const remoteError = urlParams.get('remoteError'); @@ -210,8 +216,6 @@ export class AuthService { return throwError(() => new Error(`External login error: ${remoteError}`)); } - // The backend will handle the external login and return the token - // The token is returned in the response data return this.http.get>( `${this.apiUrl}/ExternalLogin/external-login-callback`, { @@ -222,8 +226,14 @@ export class AuthService { ).pipe( tap(response => { if (response.success && response.data) { - this.handleSuccessfulLogin(response.data.token); - // Redirect to return URL after successful login + localStorage.setItem(this.tokenKey, response.data.token); + + if (response.data.user) { + this.currentUserSubject.next(response.data.user); + localStorage.setItem(this.userKey, JSON.stringify(response.data.user)); + } + + this.isAuthenticatedSubject.next(true); this.router.navigateByUrl(returnUrl); } }), @@ -298,17 +308,9 @@ export class AuthService { // Private Helper Methods // ============================== - private handleSuccessfulLogin(token: string): void { - // Store token - localStorage.setItem(this.tokenKey, token); - - // Load user data from token or fetch from API - this.loadUserData(); - - // Update authentication state - this.isAuthenticatedSubject.next(true); - } - + /** + * Load stored user on app initialization + */ private loadStoredUser(): void { const user = this.getUser(); const token = this.getToken(); @@ -316,45 +318,19 @@ export class AuthService { if (user && token) { this.currentUserSubject.next(user); this.isAuthenticatedSubject.next(true); + console.log('User restored from storage:', user); } else { this.currentUserSubject.next(null); this.isAuthenticatedSubject.next(false); } } - private loadUserData(): void { - // If we have a token but no user data, fetch it - if (this.getToken() && !this.getUser()) { - this.getCurrentUser().subscribe({ - next: (response) => { - if (response.success && response.data) { - this.currentUserSubject.next(response.data); - localStorage.setItem(this.userKey, JSON.stringify(response.data)); - } - }, - error: (error) => { - console.error('Failed to load user data:', error); - // If we can't load user data, logout - this.logout(); - } - }); - } else { - // Use stored user data - const user = this.getUser(); - if (user) { - this.currentUserSubject.next(user); - } - } - } - private handleError(error: any): Observable { let errorMessage = 'An error occurred'; if (error.error instanceof ErrorEvent) { - // Client-side error errorMessage = error.error.message; } else { - // Server-side error if (error.error && error.error.message) { errorMessage = error.error.message; } else if (error.status) { From 9f576090a780e5ea3a5c8063f4599349269422e4 Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sat, 8 Aug 2026 16:02:59 +0300 Subject: [PATCH 19/21] Update login response --- src/app/models/auth.models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/models/auth.models.ts b/src/app/models/auth.models.ts index cb6f9a5..1c8f77d 100644 --- a/src/app/models/auth.models.ts +++ b/src/app/models/auth.models.ts @@ -39,6 +39,7 @@ export interface ServiceResponse { export interface LoginResponse { token: string; + user: UserDto; returnUrl?: string; } From 9b01dfd1d3a256275eeee88051715bf174d7913f Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sat, 8 Aug 2026 16:04:12 +0300 Subject: [PATCH 20/21] Update login.component.ts to expect that user is returned after login --- src/app/views/pages/login/login.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/views/pages/login/login.component.ts b/src/app/views/pages/login/login.component.ts index 7b485b2..4301de0 100644 --- a/src/app/views/pages/login/login.component.ts +++ b/src/app/views/pages/login/login.component.ts @@ -72,7 +72,6 @@ export class LoginComponent implements OnInit { onSubmit(): void { if (this.loginForm.invalid) { - // Mark all fields as touched to trigger validation messages Object.keys(this.loginForm.controls).forEach(key => { this.loginForm.get(key)?.markAsTouched(); }); @@ -86,7 +85,8 @@ export class LoginComponent implements OnInit { next: (response) => { this.isLoading = false; if (response.success) { - // Redirect to the return URL or dashboard + // User data is already stored in AuthService + // Just redirect to the return URL this.router.navigateByUrl(this.returnUrl); } else { this.errorMessage = response.message || 'Login failed. Please try again.'; From 3033ec05f482d8216ebfd52b65be880b3cd9c59e Mon Sep 17 00:00:00 2001 From: CSGoat0 Date: Sat, 8 Aug 2026 16:33:43 +0300 Subject: [PATCH 21/21] Remove unused workflows --- .github/workflows/build-check.yml | 42 ------------------------------- .github/workflows/stale.yml | 27 -------------------- 2 files changed, 69 deletions(-) delete mode 100644 .github/workflows/build-check.yml delete mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml deleted file mode 100644 index 73f825a..0000000 --- a/.github/workflows/build-check.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Build Check - -on: - push: - branches-ignore: - - "dependabot/**" - pull_request: - branches: - - main - - v5.* - schedule: - # build runs every weekday at 4:15AM UTC - - cron: '15 4 * * *' - -env: - FORCE_COLOR: 2 - NODE: 24 - -jobs: - build: - strategy: - matrix: - platform: [ubuntu-latest, macOS-latest] - node-version: [24.x] - runs-on: ${{ matrix.platform }} - steps: - - name: Clone repository - uses: actions/checkout@v7 - - - name: Set up Node.js - uses: actions/setup-node@v6 - with: - node-version: ${{ env.node-version }} - - - name: Install npm dependencies - run: npm ci - - - name: Install Playwright Browsers - run: npx playwright install --with-deps --only-shell chromium - - - name: Run build - run: npm run build diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 163fd32..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,27 +0,0 @@ -# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. -# -# You can adjust the behavior by modifying this file. -# For more information, see: -# https://github.com/actions/stale -name: Mark stale issues and pull requests - -on: - schedule: - - cron: '34 23 * * 0' - -jobs: - stale: - - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - steps: - - uses: actions/stale@v5 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions' - stale-pr-message: 'This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions' - stale-issue-label: 'no-issue-activity' - stale-pr-label: 'no-pr-activity'