feat: add Administration section to sidebar with role-gated Member/User nav

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Chen
2026-05-27 14:26:07 -07:00
parent a18d44bd0a
commit bc67146d86
3 changed files with 46 additions and 23 deletions
@@ -35,6 +35,16 @@
{{ item.text }} {{ item.text }}
</button> </button>
</div> </div>
<!-- Administration (role-guarded) -->
<div class="nav-section" *ngIf="showAdminSection">
<h4>Administration</h4>
<button *ngFor="let item of adminNavItems" kendoButton
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
{{ item.text }}
</button>
</div>
</nav> </nav>
</div> </div>
</kendo-drawer-content> </kendo-drawer-content>
@@ -1,11 +1,12 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Router, NavigationEnd } from '@angular/router'; import { Router, NavigationEnd, RouterModule } from '@angular/router';
import { LayoutModule } from '@progress/kendo-angular-layout'; import { LayoutModule } from '@progress/kendo-angular-layout';
import { ButtonsModule } from '@progress/kendo-angular-buttons'; import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { IconsModule } from '@progress/kendo-angular-icons'; import { IconsModule } from '@progress/kendo-angular-icons';
import { SVGIcon, homeIcon, calendarIcon, userIcon } from '@progress/kendo-svg-icons'; import { SVGIcon, homeIcon, calendarIcon, userIcon, groupIcon } from '@progress/kendo-svg-icons';
import { LayoutService } from '../../../../layout/services/layout.service'; import { LayoutService } from '../../../../layout/services/layout.service';
import { AuthService, UserInfo } from '../../../../shared/services/auth.service';
import { Subject, takeUntil, filter } from 'rxjs'; import { Subject, takeUntil, filter } from 'rxjs';
interface NavItem { interface NavItem {
@@ -20,6 +21,7 @@ interface NavItem {
standalone: true, standalone: true,
imports: [ imports: [
CommonModule, CommonModule,
RouterModule,
LayoutModule, LayoutModule,
ButtonsModule, ButtonsModule,
IconsModule IconsModule
@@ -58,11 +60,19 @@ export class UserNavbarComponent implements OnInit, OnDestroy {
{ text: 'Support', icon: this.supportIcon, path: '/user-portal/support' } { text: 'Support', icon: this.supportIcon, path: '/user-portal/support' }
]; ];
public adminNavItems: NavItem[] = [
{ text: 'Members', icon: groupIcon, path: '/user-portal/admin/members' },
{ text: 'Users', icon: userIcon, path: '/user-portal/admin/users' },
];
public showAdminSection = false;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
constructor( constructor(
public layoutService: LayoutService, public layoutService: LayoutService,
private router: Router private router: Router,
private authService: AuthService
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
@@ -78,6 +88,11 @@ export class UserNavbarComponent implements OnInit, OnDestroy {
// Set initial active state // Set initial active state
this.updateActiveStates(this.router.url); this.updateActiveStates(this.router.url);
// Show Administration section for super_admin or secretary
this.authService.currentUser$.pipe(takeUntil(this.destroy$)).subscribe(user => {
this.showAdminSection = !!user?.roles?.some(r => r === 'super_admin' || r === 'secretary');
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@@ -92,11 +107,11 @@ export class UserNavbarComponent implements OnInit, OnDestroy {
private updateActiveStates(currentUrl: string): void { private updateActiveStates(currentUrl: string): void {
// Reset all active states // Reset all active states
[...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems] [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems, ...this.adminNavItems]
.forEach(item => item.active = false); .forEach(item => item.active = false);
// Set active state for current route // Set active state for current route
const activeItem = [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems] const activeItem = [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems, ...this.adminNavItems]
.find(item => currentUrl.startsWith(item.path)); .find(item => currentUrl.startsWith(item.path));
if (activeItem) { if (activeItem) {
@@ -79,29 +79,27 @@ export class UserPortalComponent implements OnInit, OnDestroy {
} }
private updatePageTitle(): void { private updatePageTitle(): void {
const url = this.router.url; const url = this.router.url;
const segments = url.split('/').filter(segment => segment); const segments = url.split('/').filter(s => s);
const key = segments.length >= 3
if (segments.length >= 2) { ? `${segments[1]}/${segments[2]}` // e.g. 'admin/members'
const page = segments[1]; : segments[1] ?? '';
this.currentPageTitle = this.getPageTitle(page); this.currentPageTitle = this.getPageTitle(key);
} else {
this.currentPageTitle = 'Dashboard';
}
} }
private getPageTitle(page: string): string { private getPageTitle(page: string): string {
const titles: { [key: string]: string } = { const titles: { [key: string]: string } = {
'dashboard': 'Dashboard', 'dashboard': 'Dashboard',
'transactions': 'Escrow Transactions', 'transactions': 'Escrow Transactions',
'tasks': 'Tasks & Todos', 'tasks': 'Tasks & Todos',
'contacts': 'Contacts', 'contacts': 'Contacts',
'documents': 'Documents', 'documents': 'Documents',
'messages': 'Messages', 'messages': 'Messages',
'settings': 'Settings' 'settings': 'Settings',
'admin/members': 'Member Management',
'admin/users': 'User Management',
}; };
return titles[page] ?? 'Dashboard';
return titles[page] || 'Dashboard';
} }
toggleSidebar(): void { toggleSidebar(): void {