Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions .github/workflows/build-check.yml

This file was deleted.

27 changes: 0 additions & 27 deletions .github/workflows/stale.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ testem.log
# System files
.DS_Store
Thumbs.db

# Environment files
src/environments/environment.ts
17 changes: 13 additions & 4 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '<router-outlet />',
imports: [RouterOutlet]
imports: [RouterOutlet],
providers: [IconSetService] // Add IconSetService as a provider
})
export class AppComponent implements OnInit {
title = 'CoreUI Angular Admin Template';
Expand All @@ -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) => {
Expand Down
10 changes: 8 additions & 2 deletions src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -26,7 +28,11 @@ export const appConfig: ApplicationConfig = {
withHashLocation()
),
IconSetService,
provideAnimationsAsync()
provideAnimationsAsync(),

// HTTP Client with functional interceptor
provideHttpClient(
withInterceptors([authInterceptor])
)
]
};

47 changes: 41 additions & 6 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -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 = [
{
Expand All @@ -12,6 +14,7 @@ export const routes: Routes = [
data: {
title: 'Home'
},
canActivate: [AuthGuard], // Protect all child routes
children: [
{
path: 'dashboard',
Expand Down Expand Up @@ -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),
Expand All @@ -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' }
];
27 changes: 27 additions & 0 deletions src/app/guards/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } 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(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.authService.isAuthenticated$.pipe(
take(1),
map(isAuthenticated => {
if (isAuthenticated) {
return true;
}

// Store the attempted URL for redirecting after login
return this.router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
})
);
}
}
52 changes: 52 additions & 0 deletions src/app/guards/role.guard.ts
Original file line number Diff line number Diff line change
@@ -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<boolean | UrlTree> | Promise<boolean | UrlTree> | 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']);
})
);
}
}
44 changes: 44 additions & 0 deletions src/app/interceptors/auth.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const router = inject(Router);

// Get the auth token from the service
const token = authService.getToken();

// 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(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) {
console.error('You do not have permission to perform this action.');
}

return throwError(() => error);
})
);
};
Loading