mesauth-angular 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # mesauth-angular
2
+
3
+ Angular helper library to connect to a backend API and SignalR hub to surface the current logged-in user and incoming notifications with dark/light theme support.
4
+
5
+ ## Changelog
6
+
7
+ ### v1.2.3 (2026-02-11) - **Fix Register Page Auto-Redirect**
8
+ - **Fixed 401 redirect on public pages**: The interceptor now skips the login redirect when the user is on `/register`, `/forgot-password`, or `/reset-password` pages. Previously, unauthenticated users on the register page were incorrectly redirected to login when any API call returned 401.
9
+
10
+ ### v1.2.2 (2026-02-06) - **Z-Index Fix for Notification UI**
11
+ - **Fixed notification panel z-index**: Increased from `1000` to `1030` to appear above CoreUI sticky table headers (`z-index: 1020`)
12
+ - **Fixed modal overlay z-index**: Increased from `9999` to `1060` to maintain proper layering hierarchy
13
+ - **Better integration with CoreUI/Bootstrap**: Follows standard z-index scale (sticky: 1020, modals: 1050+, overlays: 1060+)
14
+
15
+ ### v1.2.0 (2026-02-05) - **Notification & Auth Interceptor Fix**
16
+ - **Removed route-change user polling**: `MesAuthService` no longer re-fetches the user on every `NavigationEnd` event. User is fetched once on app init; SignalR handles real-time updates. This eliminates redundant API calls and notification toast spam on every route change.
17
+ - **Removed `fetchInitialNotifications()`**: Historical notifications were being emitted through `notifications$` Subject on every user refresh, causing toast popups for old notifications. The `notifications$` observable now only carries truly new real-time events from SignalR.
18
+ - **`refreshUser()` returns `Observable`**: Callers can now subscribe and wait for user data to load before proceeding (e.g., navigate after login). Previously returned `void`.
19
+ - **Fixed 401 redirect for expired sessions**: Removed the `!isAuthenticated` guard from the interceptor's 401 condition. When a session expires, the `BehaviorSubject` still holds stale user data, so this check was blocking the redirect to login. The `!isMeAuthPage`, `!isLoginPage`, and `!isAuthPage` guards are sufficient to prevent redirect loops.
20
+
21
+ ### v1.1.0 (2026-01-21) - **Major Update**
22
+ - 🚀 **New `provideMesAuth()` Function**: Simplified setup with a single function call
23
+ - ✨ **Functional Interceptor**: New `mesAuthInterceptor` for better compatibility with standalone apps
24
+ - 📦 **Automatic Initialization**: `provideMesAuth()` handles service initialization via `APP_INITIALIZER`
25
+ - 🔧 **Simplified API**: Just pass `apiBaseUrl` and `userBaseUrl` - no manual DI required
26
+
27
+ ### v1.0.1 (2026-01-21)
28
+ - 🔧 Internal refactoring for better module compatibility
29
+
30
+ ### v0.2.28 (2026-01-19)
31
+ - ✨ **Enhanced Avatar Support**: Direct `avatarPath` usage from user data for instant display without backend calls
32
+ - 🔄 **Improved Avatar Refresh**: Timestamp-based cache busting prevents request cancellation issues
33
+ - 🎯 **Better Change Detection**: Signal-based user updates with `ChangeDetectorRef` for reliable UI updates
34
+
35
+ ### v0.2.27 (2026-01-19)
36
+ - 🐛 Fixed avatar refresh issues in header components
37
+ - 📦 Improved build process and dependencies
38
+
39
+ ## Features
40
+
41
+ - 🔐 **Authentication**: User login/logout with API integration
42
+ - 🔔 **Real-time Notifications**: SignalR integration for live notifications
43
+ - 🎨 **Dark/Light Theme**: Automatic theme detection and support
44
+ - 🖼️ **Avatar Support**: Direct API-based avatar loading
45
+ - 🍞 **Toast Notifications**: In-app notification toasts
46
+ - 🛡️ **HTTP Interceptor**: Automatic 401/403 error handling with redirects
47
+
48
+ ## Quick Start (v1.1.0+)
49
+
50
+ ### 1. Install
51
+
52
+ ```bash
53
+ npm install mesauth-angular
54
+ ```
55
+
56
+ ### 2. Configure in app.config.ts (Recommended for Angular 14+)
57
+
58
+ ```ts
59
+ import { ApplicationConfig } from '@angular/core';
60
+ import { provideHttpClient, withInterceptors } from '@angular/common/http';
61
+ import { provideMesAuth, mesAuthInterceptor } from 'mesauth-angular';
62
+
63
+ export const appConfig: ApplicationConfig = {
64
+ providers: [
65
+ provideHttpClient(
66
+ withInterceptors([mesAuthInterceptor]) // Handles 401/403 redirects
67
+ ),
68
+ provideMesAuth({
69
+ apiBaseUrl: 'https://mes.kefico.vn/auth',
70
+ userBaseUrl: 'https://mes.kefico.vn/x' // For login/403 redirects
71
+ })
72
+ ]
73
+ };
74
+ ```
75
+
76
+ That's it! The library handles:
77
+ - Service initialization via `APP_INITIALIZER`
78
+ - `HttpClient` and `Router` injection automatically
79
+ - 401 → redirects to `{userBaseUrl}/login?returnUrl=...`
80
+ - 403 → redirects to `{userBaseUrl}/403?returnUrl=...`
81
+
82
+ ### 3. Use in Components
83
+
84
+ ```ts
85
+ import { MesAuthService } from 'mesauth-angular';
86
+
87
+ @Component({...})
88
+ export class MyComponent {
89
+ private auth = inject(MesAuthService);
90
+
91
+ // Observable streams
92
+ currentUser$ = this.auth.currentUser$;
93
+ notifications$ = this.auth.notifications$;
94
+
95
+ logout() {
96
+ this.auth.logout().subscribe();
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## Configuration Options
102
+
103
+ ```ts
104
+ interface MesAuthConfig {
105
+ apiBaseUrl: string; // Required: MesAuth API base URL
106
+ userBaseUrl?: string; // Optional: Base URL for login/403 redirects
107
+ withCredentials?: boolean; // Optional: Send cookies (default: true)
108
+ }
109
+ ```
110
+
111
+ ## Theme Support
112
+
113
+ The library automatically detects and adapts to your application's theme:
114
+
115
+ ### Automatic Theme Detection
116
+ The library checks for theme indicators on the `<html>` element:
117
+ - `class="dark"`
118
+ - `data-theme="dark"`
119
+ - `theme="dark"`
120
+ - `data-coreui-theme="dark"`
121
+
122
+ ### Dynamic Theme Changes
123
+ Theme changes are detected in real-time using `MutationObserver`, so components automatically update when your app switches themes.
124
+
125
+ ### Manual Theme Control
126
+ ```ts
127
+ import { ThemeService } from 'mesauth-angular';
128
+
129
+ // Check current theme
130
+ const currentTheme = themeService.currentTheme; // 'light' | 'dark'
131
+
132
+ // Manually set theme
133
+ themeService.setTheme('dark');
134
+
135
+ // Listen for theme changes
136
+ themeService.currentTheme$.subscribe(theme => {
137
+ console.log('Theme changed to:', theme);
138
+ });
139
+ ```
140
+
141
+ ## Avatar Loading
142
+
143
+ Avatars are loaded efficiently using multiple strategies:
144
+
145
+ ### Primary Method: Direct Path Usage
146
+ If the user object contains an `avatarPath`, it's used directly:
147
+ - **Full URLs**: Used as-is (e.g., `https://example.com/avatar.jpg`)
148
+ - **Relative Paths**: Combined with API base URL (e.g., `/uploads/avatar.jpg` → `{apiBaseUrl}/uploads/avatar.jpg`)
149
+
150
+ ### Fallback Method: API Endpoint
151
+ If no `avatarPath` is available, avatars are loaded via API:
152
+ - **API Endpoint**: `GET {apiBaseUrl}/auth/{userId}/avatar`
153
+ - **Authentication**: Uses the same credentials as other API calls
154
+
155
+ ### Cache Busting
156
+ Avatar URLs include timestamps to prevent browser caching issues during updates:
157
+ - Automatic refresh when user data changes
158
+ - Manual refresh triggers for upload/delete operations
159
+
160
+ ### Fallback Service
161
+ - **UI Avatars**: Generates initials-based avatars if no user data available
162
+ - **Authentication**: Not required for fallback avatars
163
+
164
+ ## Components
165
+
166
+ **Note:** All components are standalone and can be imported directly.
167
+
168
+ ### ma-user-profile
169
+
170
+ A reusable Angular component for displaying the current user's profile information, with options for navigation and logout.
171
+
172
+ - **Description**: Renders user details (e.g., name, avatar) fetched via the MesAuthService. Supports custom event handlers for navigation and logout actions.
173
+ - **Inputs**: None (data is sourced from the MesAuthService).
174
+ - **Outputs**:
175
+ - `onNavigate`: Emits an event when the user triggers navigation (e.g., to a profile page). Pass a handler to define behavior.
176
+ - `onLogout`: Emits an event when the user logs out. Pass a handler to perform logout logic (e.g., clear tokens, redirect).
177
+ - **Usage Example**:
178
+
179
+ ```html
180
+ <ma-user-profile
181
+ (onNavigate)="handleNavigation($event)"
182
+ (onLogout)="handleLogout()">
183
+ </ma-user-profile>
184
+ ```
185
+
186
+ In your component's TypeScript file:
187
+
188
+ ```ts
189
+ handleNavigation(event: any) {
190
+ // Navigate to user profile page
191
+ this.router.navigate(['/profile']);
192
+ }
193
+
194
+ handleLogout() {
195
+ // Perform logout, e.g., clear session and redirect
196
+ this.mesAuth.logout(); // Assuming a logout method exists
197
+ this.router.navigate(['/login']);
198
+ }
199
+ ```
200
+
201
+ ### ma-notification-panel
202
+
203
+ A standalone component for displaying a slide-out notification panel with real-time updates.
204
+
205
+ - **Description**: Shows a list of notifications, allows marking as read/delete, and integrates with toast notifications for new alerts.
206
+ - **Inputs**: None.
207
+ - **Outputs**: None (uses internal methods for actions).
208
+ - **Usage Example**:
209
+
210
+ ```html
211
+ <ma-notification-panel #notificationPanel></ma-notification-panel>
212
+ ```
213
+
214
+ In your component:
215
+
216
+ ```ts
217
+ // To open the panel
218
+ notificationPanel.open();
219
+ ```
220
+
221
+ ## Migration Guide
222
+
223
+ ### Upgrading from v0.x to v1.1.0+
224
+
225
+ The setup has been greatly simplified. Here's how to migrate:
226
+
227
+ **Before (v0.x):**
228
+ ```ts
229
+ // app.config.ts - OLD WAY
230
+ import { MesAuthModule, MesAuthService } from 'mesauth-angular';
231
+ import { HTTP_INTERCEPTORS } from '@angular/common/http';
232
+
233
+ export const appConfig: ApplicationConfig = {
234
+ providers: [
235
+ importProvidersFrom(MesAuthModule),
236
+ { provide: HTTP_INTERCEPTORS, useClass: MesAuthInterceptor, multi: true }
237
+ ]
238
+ };
239
+
240
+ // app.component.ts - OLD WAY
241
+ export class AppComponent {
242
+ constructor() {
243
+ this.mesAuthService.init({
244
+ apiBaseUrl: '...',
245
+ userBaseUrl: '...'
246
+ }, inject(HttpClient), inject(Router));
247
+ }
248
+ }
249
+ ```
250
+
251
+ **After (v1.1.0+):**
252
+ ```ts
253
+ // app.config.ts - NEW WAY (everything in one place!)
254
+ import { provideMesAuth, mesAuthInterceptor } from 'mesauth-angular';
255
+
256
+ export const appConfig: ApplicationConfig = {
257
+ providers: [
258
+ provideHttpClient(withInterceptors([mesAuthInterceptor])),
259
+ provideMesAuth({
260
+ apiBaseUrl: 'https://mes.kefico.vn/auth',
261
+ userBaseUrl: 'https://mes.kefico.vn/x'
262
+ })
263
+ ]
264
+ };
265
+
266
+ // app.component.ts - No init() needed!
267
+ export class AppComponent {
268
+ // Just inject and use - no manual initialization required
269
+ }
270
+ ```
271
+
272
+ **Key Changes:**
273
+ - `provideMesAuth()` replaces `MesAuthModule` + manual `init()` call
274
+ - `mesAuthInterceptor` (functional) replaces `MesAuthInterceptor` (class-based)
275
+ - No need to inject `HttpClient` or `Router` manually
276
+ - Configuration moved from `AppComponent` to `app.config.ts`
277
+
278
+ ## Troubleshooting
279
+
280
+ ### JIT Compiler Error in Production or AOT Mode
281
+ If you encounter an error like "The injectable 'MesAuthService' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available," this typically occurs because:
282
+ - The package is being imported directly from source code (e.g., during development) without building it first.
283
+ - The client app is running in AOT (Ahead-of-Time) compilation mode, which requires pre-compiled libraries.
284
+
285
+ **Solutions:**
286
+ 1. **Build the package for production/AOT compatibility:**
287
+ - Ensure you have built the package using `npm run build` (which uses ng-packagr or similar to generate AOT-ready code).
288
+ - Install the built package via npm (e.g., from a local tarball or registry) instead of linking to the source folder.
289
+
290
+ 2. **For development (if you must link to source):**
291
+ - Switch your Angular app to JIT mode by bootstrapping with `@angular/platform-browser-dynamic` instead of `@angular/platform-browser`.
292
+
293
+ 3. **Verify imports:**
294
+ - Ensure you're importing from the built package (e.g., `import { MesAuthService } from 'mesauth-angular';`) and not from the `src` folder.
295
+
296
+ ### Components Appear Empty
297
+ If components like `ma-user` or `ma-user-profile` render as empty:
298
+ - Ensure `provideMesAuth()` is called in your `app.config.ts`.
299
+ - Check browser console for logs from components.
300
+ - If `currentUser` is null, the component shows a login button—verify the API returns user data.
301
+
302
+ ## Notes
303
+ - The service expects an endpoint `GET {apiBaseUrl}/auth/me` that returns the current user.
304
+ - Avatar endpoint: `GET {apiBaseUrl}/auth/{userId}/avatar`
305
+ - SignalR events used: `ReceiveNotification` (adjust to your backend).
@@ -8,6 +8,7 @@ import * as i2 from '@angular/router';
8
8
  import { Router } from '@angular/router';
9
9
  import * as i3 from '@angular/common';
10
10
  import { NgIf, CommonModule, NgFor } from '@angular/common';
11
+ import { DomSanitizer } from '@angular/platform-browser';
11
12
 
12
13
  /** Injection token for MesAuth configuration */
13
14
  const MES_AUTH_CONFIG = new InjectionToken('MES_AUTH_CONFIG');
@@ -777,9 +778,11 @@ class NotificationPanelComponent {
777
778
  getNotificationMessage(notification) {
778
779
  return notification.message || '';
779
780
  }
780
- // Returns HTML message for modal display
781
+ sanitizer = inject(DomSanitizer);
782
+ // Returns HTML message for modal display (bypasses Angular sanitizer to preserve inline styles)
781
783
  getHtmlMessage(notification) {
782
- return notification.messageHtml || notification.message || '';
784
+ const html = notification.messageHtml || notification.message || '';
785
+ return this.sanitizer.bypassSecurityTrustHtml(html);
783
786
  }
784
787
  constructor(authService, toastService, themeService) {
785
788
  this.authService = authService;
@@ -903,7 +906,13 @@ class NotificationPanelComponent {
903
906
  });
904
907
  }
905
908
  formatDate(dateString) {
906
- const date = new Date(dateString);
909
+ // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
910
+ const normalizedDateString = this.parseUtcDate(dateString);
911
+ const date = new Date(normalizedDateString);
912
+ // Check if the date is valid
913
+ if (isNaN(date.getTime())) {
914
+ return 'Invalid date';
915
+ }
907
916
  const now = new Date();
908
917
  const diffMs = now.getTime() - date.getTime();
909
918
  const diffMins = Math.floor(diffMs / 60000);
@@ -919,6 +928,17 @@ class NotificationPanelComponent {
919
928
  return `${diffDays}d ago`;
920
929
  return date.toLocaleDateString();
921
930
  }
931
+ // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
932
+ parseUtcDate(dateStr) {
933
+ // Handle date strings that might be missing the 'T' separator
934
+ // Convert formats like "2023-12-01 12:30:45" to "2023-12-01T12:30:45"
935
+ let normalized = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T');
936
+ // If no timezone indicator, assume UTC by appending 'Z'
937
+ if (!normalized.endsWith('Z') && !normalized.includes('+') && !normalized.includes('-', 10)) {
938
+ normalized += 'Z';
939
+ }
940
+ return normalized;
941
+ }
922
942
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
923
943
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
924
944
  <div class="notification-panel" [class.open]="isOpen">
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mesauth-angular.mjs","sources":["../../src/mes-auth.service.ts","../../src/mes-auth.interceptor.ts","../../src/mes-auth.module.ts","../../src/theme.service.ts","../../src/user-profile.component.ts","../../src/toast.service.ts","../../src/toast-container.component.ts","../../src/notification-panel.component.ts","../../src/ma-user.component.ts","../../src/notification-badge.component.ts","../../src/mesauth-angular.ts"],"sourcesContent":["import { inject, Injectable, InjectionToken, EnvironmentProviders, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\r\nimport { BehaviorSubject, Subject, Observable, of, EMPTY } from 'rxjs';\r\nimport { tap, catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\n\r\nexport interface MesAuthConfig {\r\n apiBaseUrl: string;\r\n withCredentials?: boolean;\r\n userBaseUrl?: string;\r\n}\r\n\r\n/** Injection token for MesAuth configuration */\r\nexport const MES_AUTH_CONFIG = new InjectionToken<MesAuthConfig>('MES_AUTH_CONFIG');\r\n\r\n/**\r\n * Provides MesAuth with configuration.\r\n * This is the recommended way to set up mesauth-angular in standalone apps.\r\n *\r\n * @example\r\n * ```typescript\r\n * // app.config.ts\r\n * export const appConfig: ApplicationConfig = {\r\n * providers: [\r\n * provideHttpClient(withInterceptors([mesAuthInterceptor])),\r\n * provideMesAuth({\r\n * apiBaseUrl: 'https://auth.example.com',\r\n * userBaseUrl: 'https://app.example.com'\r\n * })\r\n * ]\r\n * };\r\n * ```\r\n */\r\nexport function provideMesAuth(config: MesAuthConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: MES_AUTH_CONFIG, useValue: config },\r\n MesAuthService,\r\n provideAppInitializer(() => {\r\n const mesAuthService = inject(MesAuthService);\r\n const httpClient = inject(HttpClient);\r\n const router = inject(Router);\r\n mesAuthService.init(config, httpClient, router);\r\n })\r\n ]);\r\n}\r\n\r\nexport interface IUser {\r\n userId?: string;\r\n userName?: string;\r\n fullName?: string;\r\n gender?: string;\r\n email?: string;\r\n phoneNumber?: string;\r\n department?: string;\r\n position?: string;\r\n tokenVersion?: string;\r\n permEndpoint?: string;\r\n perms?: Set<string>;\r\n employeeCode?: string;\r\n avatarPath?: string;\r\n loginMethod?: number;\r\n hrFullNameVn?: string;\r\n hrFullNameEn?: string;\r\n hrPosition?: string;\r\n hrJobTitle?: string;\r\n hrGender?: string;\r\n hrMobile?: string;\r\n hrEmail?: string;\r\n hrJoinDate?: string;\r\n hrBirthDate?: string;\r\n hrWorkStatus?: string;\r\n hrDoiTuong?: string;\r\n hrTeamCode?: string;\r\n hrLineCode?: string;\r\n}\r\n\r\nexport enum NotificationType {\r\n Info = 'Info',\r\n Warning = 'Warning',\r\n Error = 'Error',\r\n Success = 'Success'\r\n}\r\n\r\nexport interface NotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n isRead: boolean;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\nexport interface FrontEndRoute {\r\n id: number;\r\n roleId: string;\r\n roleName: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n children: FrontEndRoute[];\r\n}\r\n\r\nexport interface UserFrontEndRoutesGrouped {\r\n appId: string;\r\n appName: string;\r\n feUrl?: string;\r\n routes: FrontEndRoute[];\r\n}\r\n\r\nexport interface FrontEndRouteMaster {\r\n id: number;\r\n appId: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n}\r\n\r\nexport interface CreateFrontEndRouteDto {\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder?: number;\r\n isLabel?: boolean;\r\n}\r\n\r\nexport interface PagedList<T> {\r\n items: T[];\r\n totalCount: number;\r\n page: number;\r\n pageSize: number;\r\n totalPages: number;\r\n hasNext: boolean;\r\n hasPrevious: boolean;\r\n}\r\n\r\nexport interface RealTimeNotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\n@Injectable()\r\nexport class MesAuthService {\r\n private hubConnection: HubConnection | null = null;\r\n private _currentUser = new BehaviorSubject<IUser | null>(null);\r\n public currentUser$: Observable<IUser | null> = this._currentUser.asObservable();\r\n private _notifications = new Subject<any>();\r\n public notifications$: Observable<any> = this._notifications.asObservable();\r\n\r\n private apiBase = '';\r\n private config: MesAuthConfig | null = null;\r\n private http!: HttpClient;\r\n private router?: Router;\r\n\r\n constructor() {\r\n // Empty constructor - all dependencies passed to init()\r\n }\r\n\r\n init(config: MesAuthConfig, httpClient: HttpClient, router?: Router) {\r\n this.config = config;\r\n this.http = httpClient;\r\n this.router = router;\r\n this.apiBase = config.apiBaseUrl.replace(/\\/$/, '');\r\n\r\n // Fetch user once on init. Route changes do NOT re-fetch the user.\r\n // Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.\r\n // SignalR handles real-time notification delivery without polling.\r\n this.fetchCurrentUser().subscribe();\r\n }\r\n\r\n getConfig(): MesAuthConfig | null {\r\n return this.config;\r\n }\r\n\r\n private fetchCurrentUser(): Observable<any> {\r\n if (!this.apiBase) return EMPTY;\r\n const url = `${this.apiBase}/auth/me`;\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap((u) => {\r\n this._currentUser.next(u);\r\n if (u && this.config) {\r\n this.startConnection(this.config);\r\n }\r\n }),\r\n catchError((err) => {\r\n // Silently handle auth errors (401/403) - user is not logged in\r\n if (err.status === 401 || err.status === 403) {\r\n this._currentUser.next(null);\r\n }\r\n return of(null);\r\n })\r\n );\r\n }\r\n\r\n public getUnreadCount(): Observable<any> {\r\n return this.http.get(`${this.apiBase}/notif/me/unread-count`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public getNotifications(page: number = 1, pageSize: number = 20, includeRead: boolean = false, type?: string): Observable<any> {\r\n let url = `${this.apiBase}/notif/me?page=${page}&pageSize=${pageSize}&includeRead=${includeRead}`;\r\n if (type) {\r\n url += `&type=${type}`;\r\n }\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAsRead(notificationId: string): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/${notificationId}/read`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAllAsRead(): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/me/read-all`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public deleteNotification(notificationId: string): Observable<any> {\r\n return this.http.delete(`${this.apiBase}/notif/${notificationId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get frontend routes assigned to the current user\r\n * Returns routes grouped by application\r\n */\r\n public getFrontEndRoutes(): Observable<UserFrontEndRoutesGrouped[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<UserFrontEndRoutesGrouped[]>(`${this.apiBase}/fe-routes/me`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get master routes for a specific application\r\n * @param appId - The application ID\r\n */\r\n public getRouteMasters(appId: string): Observable<FrontEndRouteMaster[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<FrontEndRouteMaster[]>(`${this.apiBase}/fe-routes/masters/${appId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Register/sync frontend routes for an application\r\n * This is typically called on app startup to sync routes from the frontend app\r\n * @param appId - The application ID (passed via X-App-Id header)\r\n * @param routes - Array of route definitions\r\n */\r\n public registerFrontEndRoutes(appId: string, routes: CreateFrontEndRouteDto[]): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n const headers = { 'X-App-Id': appId };\r\n return this.http.post(`${this.apiBase}/fe-routes/register`, routes, {\r\n headers,\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Create a new route master\r\n * @param appId - The application ID\r\n * @param route - Route details\r\n */\r\n public createRouteMaster(appId: string, route: CreateFrontEndRouteDto): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters`, {\r\n appId,\r\n ...route\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Update an existing route master\r\n * @param routeId - The route master ID\r\n * @param route - Updated route details\r\n */\r\n public updateRouteMaster(routeId: number, route: Partial<CreateFrontEndRouteDto> & { isActive?: boolean }): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.put<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters/${routeId}`, route, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Delete a route master\r\n * @param routeId - The route master ID\r\n */\r\n public deleteRouteMaster(routeId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/masters/${routeId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Assign a route to a role\r\n * @param routeMasterId - The route master ID\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public assignRouteToRole(routeMasterId: number, roleId: string): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post(`${this.apiBase}/fe-routes/mappings`, {\r\n routeMasterId,\r\n roleId\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Remove a route assignment from a role\r\n * @param mappingId - The mapping ID\r\n */\r\n public removeRouteFromRole(mappingId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/mappings/${mappingId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Get route-to-role mappings for a specific role\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public getRouteMappingsByRole(roleId: string): Observable<any[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<any[]>(`${this.apiBase}/fe-routes/mappings?roleId=${roleId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n private startConnection(config: MesAuthConfig) {\r\n if (this.hubConnection) return;\r\n const signalrUrl = config.apiBaseUrl.replace(/\\/$/, '') + '/hub/notification';\r\n const builder = new HubConnectionBuilder()\r\n .withUrl(signalrUrl, { withCredentials: config.withCredentials ?? true })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning);\r\n\r\n this.hubConnection = builder.build();\r\n\r\n this.hubConnection.on('ReceiveNotification', (n: any) => {\r\n this._notifications.next(n);\r\n });\r\n\r\n this.hubConnection.start().then(() => {}).catch((err) => {});\r\n\r\n this.hubConnection.onclose(() => {});\r\n this.hubConnection.onreconnecting(() => {});\r\n this.hubConnection.onreconnected(() => {});\r\n }\r\n\r\n public stop() {\r\n if (!this.hubConnection) return;\r\n this.hubConnection.stop().catch(() => {});\r\n this.hubConnection = null;\r\n }\r\n\r\n public logout(): Observable<any> {\r\n const url = `${this.apiBase}/auth/logout`;\r\n return this.http.post(url, {}, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap(() => {\r\n this._currentUser.next(null);\r\n this.stop();\r\n })\r\n );\r\n }\r\n\r\n public get currentUser(): IUser | null {\r\n return this._currentUser.value;\r\n }\r\n\r\n public get isAuthenticated(): boolean {\r\n return this._currentUser.value !== null;\r\n }\r\n\r\n /**\r\n * Refreshes the current user from the server.\r\n * Returns an Observable that completes when the user data is loaded.\r\n * Callers can subscribe to wait for completion before proceeding (e.g., navigating after login).\r\n */\r\n public refreshUser(): Observable<any> {\r\n return this.fetchCurrentUser();\r\n }\r\n}\r\n","import { inject } from '@angular/core';\r\nimport { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';\r\nimport { throwError } from 'rxjs';\r\nimport { catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n// Track if we're currently redirecting to prevent loopback\r\nlet isRedirecting = false;\r\n\r\n/**\r\n * Functional HTTP interceptor for handling 401/403 auth errors.\r\n * Redirects to login page on 401, and to 403 page on 403.\r\n * Includes loopback prevention to avoid infinite redirects.\r\n */\r\nexport const mesAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const authService = inject(MesAuthService);\r\n const router = inject(Router);\r\n\r\n return next(req).pipe(\r\n catchError((error: HttpErrorResponse) => {\r\n const status = error.status;\r\n\r\n // Check if we should handle this error and prevent loopback\r\n if ((status === 401 || status === 403) && !isRedirecting) {\r\n const config = authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n\r\n const currentUrl = router.url + (window.location.hash || '');\r\n const returnUrl = encodeURIComponent(currentUrl);\r\n\r\n // Avoid loops if already on auth/unauth pages\r\n const isLoginPage = currentUrl.includes('/login');\r\n const is403Page = currentUrl.includes('/403');\r\n const isAuthPage = currentUrl.includes('/auth');\r\n // Public pages that should never trigger a 401 redirect (e.g., register, password reset)\r\n const isPublicPage = currentUrl.includes('/register')\r\n || currentUrl.includes('/forgot-password')\r\n || currentUrl.includes('/reset-password');\r\n // Skip redirect for the initial /auth/me check (app startup when not logged in)\r\n const isMeAuthPage = req.url.includes('/auth/me');\r\n\r\n if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage && !isPublicPage) {\r\n // Session expired or not authenticated - redirect to login\r\n // No isAuthenticated check: when session expires, BehaviorSubject still holds\r\n // stale user data, so checking isAuthenticated would block the redirect.\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n } else if (status === 403 && !is403Page) {\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n let redirectUrl = `${baseUrl}/403?returnUrl=${returnUrl}`;\r\n if (error.error && error.error.required) {\r\n redirectUrl += `&required=${encodeURIComponent(error.error.required)}`;\r\n }\r\n window.location.href = redirectUrl;\r\n }\r\n }\r\n return throwError(() => error);\r\n })\r\n );\r\n};\r\n","import { NgModule } from '@angular/core';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n@NgModule({\r\n providers: [\r\n MesAuthService\r\n ]\r\n})\r\nexport class MesAuthModule {}\r\n","import { Injectable, OnDestroy } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport type Theme = 'light' | 'dark';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ThemeService implements OnDestroy {\r\n private _currentTheme = new BehaviorSubject<Theme>('light');\r\n public currentTheme$: Observable<Theme> = this._currentTheme.asObservable();\r\n private observer: MutationObserver | null = null;\r\n\r\n constructor() {\r\n this.detectTheme();\r\n this.startWatching();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.stopWatching();\r\n }\r\n\r\n private detectTheme(): void {\r\n const html = document.documentElement;\r\n const isDark = html.classList.contains('dark') ||\r\n html.getAttribute('data-theme') === 'dark' ||\r\n html.getAttribute('theme') === 'dark' ||\r\n html.getAttribute('data-coreui-theme') === 'dark';\r\n\r\n this._currentTheme.next(isDark ? 'dark' : 'light');\r\n }\r\n\r\n private startWatching(): void {\r\n if (typeof MutationObserver === 'undefined') {\r\n // Fallback for older browsers - check periodically\r\n setInterval(() => this.detectTheme(), 1000);\r\n return;\r\n }\r\n\r\n this.observer = new MutationObserver(() => {\r\n this.detectTheme();\r\n });\r\n\r\n this.observer.observe(document.documentElement, {\r\n attributes: true,\r\n attributeFilter: ['class', 'data-theme', 'theme', 'data-coreui-theme']\r\n });\r\n }\r\n\r\n private stopWatching(): void {\r\n if (this.observer) {\r\n this.observer.disconnect();\r\n this.observer = null;\r\n }\r\n }\r\n\r\n get currentTheme(): Theme {\r\n return this._currentTheme.value;\r\n }\r\n\r\n // Method to manually set theme if needed\r\n setTheme(theme: Theme): void {\r\n this._currentTheme.next(theme);\r\n }\r\n\r\n // Re-detect theme from DOM\r\n refreshTheme(): void {\r\n this.detectTheme();\r\n }\r\n}","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding, HostListener, signal, ChangeDetectorRef } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService, IUser } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-user-profile',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <div class=\"user-profile-container\">\r\n <!-- Not logged in -->\r\n <ng-container *ngIf=\"!currentUser()\">\r\n <button class=\"login-btn\" (click)=\"onLogin()\">\r\n Login\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Logged in -->\r\n <ng-container *ngIf=\"currentUser()\">\r\n <div class=\"user-header\">\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n\r\n <div class=\"user-menu-wrapper\">\r\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\">\r\n <img \r\n *ngIf=\"currentUser().fullName || currentUser().userName\"\r\n [src]=\"getAvatarUrl(currentUser())\" \r\n [alt]=\"currentUser().fullName || currentUser().userName\"\r\n class=\"avatar\"\r\n />\r\n <span *ngIf=\"!(currentUser().fullName || currentUser().userName)\" class=\"avatar-initial\">\r\n {{ getLastNameInitial(currentUser()) }}\r\n </span>\r\n </button>\r\n\r\n <div class=\"mes-dropdown-menu\" *ngIf=\"dropdownOpen\">\r\n <div class=\"mes-dropdown-header\">\r\n {{ currentUser().fullName || currentUser().userName }}\r\n </div>\r\n <button class=\"mes-dropdown-item profile-link\" (click)=\"onViewProfile()\">\r\n View Profile\r\n </button>\r\n <button class=\"mes-dropdown-item logout-item\" (click)=\"onLogout()\">\r\n Logout\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --primary-color: #1976d2;\r\n --primary-hover: #1565c0;\r\n --primary-light: rgba(25, 118, 210, 0.1);\r\n --error-color: #f44336;\r\n --error-light: #ffebee;\r\n --text-primary: #333;\r\n --text-secondary: #666;\r\n --text-muted: #999;\r\n --bg-primary: white;\r\n --bg-secondary: #f5f5f5;\r\n --bg-tertiary: #fafafa;\r\n --bg-hover: #f5f5f5;\r\n --border-color: #e0e0e0;\r\n --border-light: #f0f0f0;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --shadow-light: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --primary-color: #90caf9;\r\n --primary-hover: #64b5f6;\r\n --primary-light: rgba(144, 202, 249, 0.1);\r\n --error-color: #ef5350;\r\n --error-light: rgba(239, 83, 80, 0.1);\r\n --text-primary: #e0e0e0;\r\n --text-secondary: #b0b0b0;\r\n --text-muted: #888;\r\n --bg-primary: #1e1e1e;\r\n --bg-secondary: #2d2d2d;\r\n --bg-tertiary: #252525;\r\n --bg-hover: #333;\r\n --border-color: #404040;\r\n --border-light: #333;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --shadow-light: rgba(0, 0, 0, 0.2);\r\n }\r\n\r\n .user-profile-container {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n padding: 0 16px;\r\n }\r\n\r\n .login-btn {\r\n padding: 8px 16px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n transition: background-color 0.3s;\r\n }\r\n\r\n .login-btn:hover {\r\n background-color: var(--primary-hover);\r\n }\r\n\r\n .user-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n\r\n .user-menu-wrapper {\r\n position: relative;\r\n }\r\n\r\n .user-menu-btn {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 50%;\r\n transition: background-color 0.2s;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n\r\n .user-menu-btn:hover {\r\n background-color: var(--primary-light);\r\n }\r\n\r\n .avatar {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n object-fit: cover;\r\n background-color: #e0e0e0;\r\n }\r\n\r\n .avatar-initial {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n background-color: var(--primary-color);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-weight: bold;\r\n font-size: 16px;\r\n }\r\n\r\n .mes-dropdown-menu {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n box-shadow: 0 2px 8px var(--shadow);\r\n min-width: 200px;\r\n z-index: 1000;\r\n overflow: hidden;\r\n }\r\n\r\n .mes-dropdown-header {\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--border-light);\r\n font-weight: 600;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n }\r\n\r\n .mes-dropdown-item {\r\n display: block;\r\n width: 100%;\r\n padding: 12px 16px;\r\n border: none;\r\n background: none;\r\n text-align: left;\r\n cursor: pointer;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n text-decoration: none;\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .mes-dropdown-item:hover {\r\n background-color: var(--bg-hover);\r\n }\r\n\r\n .profile-link {\r\n color: var(--primary-color);\r\n }\r\n\r\n .logout-item {\r\n border-top: 1px solid var(--border-light);\r\n color: var(--error-color);\r\n }\r\n\r\n .logout-item:hover {\r\n background-color: var(--error-light);\r\n }\r\n\r\n .user-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n }\r\n\r\n .user-name {\r\n font-weight: 500;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .user-position {\r\n font-size: 12px;\r\n color: var(--text-secondary);\r\n }\r\n\r\n .logout-btn {\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n color: var(--text-secondary);\r\n padding: 4px 8px;\r\n transition: color 0.2s;\r\n }\r\n\r\n .logout-btn:hover {\r\n color: var(--primary-color);\r\n }\r\n\r\n @media (max-width: 768px) {\r\n .user-info {\r\n display: none;\r\n }\r\n\r\n .avatar {\r\n width: 32px;\r\n height: 32px;\r\n }\r\n }\r\n `]\r\n})\r\nexport class UserProfileComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n currentUser = signal<IUser | null>(null);\r\n currentTheme: Theme = 'light';\r\n unreadCount = 0;\r\n dropdownOpen = false;\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n // Signal to force avatar refresh\r\n avatarRefresh = signal<number>(Date.now());\r\n\r\n constructor(private authService: MesAuthService, private router: Router, private themeService: ThemeService, private cdr: ChangeDetectorRef) {}\r\n\r\n ngOnInit() {\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.currentUser.set(user);\r\n this.hasUser = !!user;\r\n // Force avatar refresh when user changes\r\n this.avatarRefresh.set(Date.now());\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n } else {\r\n this.loadUnreadCount();\r\n }\r\n this.cdr.markForCheck();\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n // Listen for new real-time notifications (SignalR only)\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n getAvatarUrl(user: IUser): string {\r\n // Use the refresh signal to force update\r\n const refresh = this.avatarRefresh();\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.apiBaseUrl || '';\r\n \r\n // If user has avatarPath, use it directly\r\n if (user.avatarPath) {\r\n // If avatarPath is already a full URL, use it as-is\r\n if (user.avatarPath.startsWith('http://') || user.avatarPath.startsWith('https://')) {\r\n return user.avatarPath;\r\n }\r\n // If it's a relative path, construct full URL with refresh timestamp\r\n return `${baseUrl.replace(/\\/$/, '')}${user.avatarPath}?t=${refresh}`;\r\n }\r\n\r\n // Fallback: construct URL using userId\r\n const userId = user.userId;\r\n if (userId && baseUrl) {\r\n return `${baseUrl.replace(/\\/$/, '')}/auth/${userId}/avatar?t=${refresh}`;\r\n }\r\n \r\n // Fallback to UI avatars service if no userId or baseUrl\r\n const displayName = user.userName || user.userId || 'User';\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;\r\n }\r\n\r\n getLastNameInitial(user: IUser): string {\r\n const fullName = user.fullName || user.userName || 'U';\r\n const parts = fullName.split(' ');\r\n const lastPart = parts[parts.length - 1];\r\n return lastPart.charAt(0).toUpperCase();\r\n }\r\n\r\n toggleDropdown() {\r\n this.dropdownOpen = !this.dropdownOpen;\r\n }\r\n\r\n @HostListener('document:click', ['$event'])\r\n onDocumentClick(event: Event) {\r\n const target = event.target as HTMLElement;\r\n const clickedInside = target.closest('.user-menu-wrapper');\r\n if (!clickedInside) {\r\n this.dropdownOpen = false;\r\n }\r\n }\r\n\r\n onLogin() {\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(this.router.url);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n }\r\n\r\n onViewProfile() {\r\n this.router.navigate(['/profile']);\r\n this.dropdownOpen = false;\r\n }\r\n\r\n onLogout() {\r\n this.authService.logout().subscribe({\r\n next: () => {\r\n // Clear current user after successful logout\r\n this.dropdownOpen = false;\r\n \r\n // Navigate to login with return URL\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(window.location.href);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n },\r\n error: (err) => {\r\n // Still navigate to login even if logout fails\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n window.location.href = `${baseUrl}/login`;\r\n }\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n\r\n","import { Injectable } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport interface Toast {\r\n id: string;\r\n message: string;\r\n title?: string;\r\n type: 'info' | 'success' | 'warning' | 'error';\r\n duration?: number;\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ToastService {\r\n private toasts$ = new BehaviorSubject<Toast[]>([]);\r\n public toasts: Observable<Toast[]> = this.toasts$.asObservable();\r\n\r\n show(message: string, title?: string, type: 'info' | 'success' | 'warning' | 'error' = 'info', duration: number = 5000) {\r\n const id = Math.random().toString(36).substr(2, 9);\r\n const toast: Toast = {\r\n id,\r\n message,\r\n title,\r\n type,\r\n duration\r\n };\r\n\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next([...currentToasts, toast]);\r\n\r\n if (duration > 0) {\r\n setTimeout(() => {\r\n this.remove(id);\r\n }, duration);\r\n }\r\n\r\n return id;\r\n }\r\n\r\n remove(id: string) {\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next(currentToasts.filter(t => t.id !== id));\r\n }\r\n\r\n clear() {\r\n this.toasts$.next([]);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { ToastService, Toast } from './toast.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-toast-container',\r\n standalone: true,\r\n imports: [CommonModule],\r\n template: `\r\n <div class=\"toast-container\">\r\n <div \r\n *ngFor=\"let toast of toasts\"\r\n class=\"toast\"\r\n [class]=\"'toast-' + toast.type\"\r\n [@slideIn]\r\n >\r\n <div class=\"toast-content\">\r\n <div *ngIf=\"toast.title\" class=\"toast-title\">{{ toast.title }}</div>\r\n <div class=\"toast-message\" [innerHTML]=\"toast.message\"></div>\r\n </div>\r\n <button class=\"toast-close\" (click)=\"close(toast.id)\" aria-label=\"Close\">\r\n ✕\r\n </button>\r\n </div>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --info-color: #2196f3;\r\n --success-color: #4caf50;\r\n --warning-color: #ff9800;\r\n --error-color: #f44336;\r\n --text-primary: #333;\r\n --bg-primary: white;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --text-secondary: #999;\r\n --border-color: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --info-color: #64b5f6;\r\n --success-color: #81c784;\r\n --warning-color: #ffb74d;\r\n --error-color: #ef5350;\r\n --text-primary: #e0e0e0;\r\n --bg-primary: #1e1e1e;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --text-secondary: #888;\r\n --border-color: rgba(255, 255, 255, 0.1);\r\n }\r\n\r\n .toast-container {\r\n position: fixed;\r\n top: 20px;\r\n right: 20px;\r\n z-index: 9999;\r\n pointer-events: none;\r\n }\r\n\r\n .toast {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n margin-bottom: 12px;\r\n border-radius: 4px;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n box-shadow: 0 4px 12px var(--shadow);\r\n pointer-events: auto;\r\n min-width: 280px;\r\n max-width: 400px;\r\n animation: slideIn 0.3s ease-out;\r\n }\r\n\r\n .toast-content {\r\n flex: 1;\r\n }\r\n\r\n .toast-title {\r\n font-weight: 600;\r\n font-size: 14px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n .toast-message {\r\n font-size: 13px;\r\n line-height: 1.4;\r\n }\r\n\r\n .toast-close {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 18px;\r\n color: var(--text-secondary);\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .toast-close:hover {\r\n color: var(--text-primary);\r\n }\r\n\r\n /* Toast types */\r\n .toast-info {\r\n border-left: 4px solid var(--info-color);\r\n }\r\n\r\n .toast-info .toast-title {\r\n color: var(--info-color);\r\n }\r\n\r\n .toast-info .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-success {\r\n border-left: 4px solid var(--success-color);\r\n }\r\n\r\n .toast-success .toast-title {\r\n color: var(--success-color);\r\n }\r\n\r\n .toast-success .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-warning {\r\n border-left: 4px solid var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-title {\r\n color: var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-error {\r\n border-left: 4px solid var(--error-color);\r\n }\r\n\r\n .toast-error .toast-title {\r\n color: var(--error-color);\r\n }\r\n\r\n .toast-error .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n @keyframes slideIn {\r\n from {\r\n transform: translateX(400px);\r\n opacity: 0;\r\n }\r\n to {\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n }\r\n\r\n @media (max-width: 600px) {\r\n .toast-container {\r\n top: 10px;\r\n right: 10px;\r\n left: 10px;\r\n }\r\n\r\n .toast {\r\n min-width: auto;\r\n max-width: 100%;\r\n }\r\n }\r\n `]\r\n})\r\nexport class ToastContainerComponent implements OnInit, OnDestroy {\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n toasts: Toast[] = [];\r\n currentTheme: Theme = 'light';\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private toastService: ToastService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.toastService.toasts\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(toasts => {\r\n this.toasts = toasts;\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n close(id: string) {\r\n this.toastService.remove(id);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding, Output, EventEmitter, inject } from '@angular/core';\r\nimport { NgIf, NgFor } from '@angular/common';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\nimport { MesAuthService, NotificationDto, PagedList, RealTimeNotificationDto } from './mes-auth.service';\r\nimport { ToastService } from './toast.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-notification-panel',\r\n standalone: true,\r\n imports: [NgIf, NgFor],\r\n template: `\r\n <div class=\"notification-panel\" [class.open]=\"isOpen\">\r\n <!-- Header -->\r\n <div class=\"panel-header\">\r\n <h3>Notifications</h3>\r\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\">✕</button>\r\n </div>\r\n\r\n <!-- Tabs -->\r\n <div class=\"tabs\">\r\n <button\r\n class=\"tab-btn\"\r\n [class.active]=\"activeTab === 'unread'\"\r\n (click)=\"switchTab('unread')\"\r\n >\r\n Unread ({{ unreadNotifications.length }})\r\n </button>\r\n <button\r\n class=\"tab-btn\"\r\n [class.active]=\"activeTab === 'read'\"\r\n (click)=\"switchTab('read')\"\r\n >\r\n Read ({{ readNotifications.length }})\r\n </button>\r\n </div>\r\n\r\n <!-- Notifications List -->\r\n <div class=\"notifications-list\">\r\n <ng-container *ngIf=\"currentNotifications.length > 0\">\r\n <div\r\n *ngFor=\"let notification of currentNotifications\"\r\n class=\"notification-item\"\r\n [class.unread]=\"!notification.isRead\"\r\n (click)=\"openDetails(notification)\"\r\n >\r\n <div class=\"notification-content\">\r\n <div class=\"notification-title\">{{ notification.title }}</div>\r\n <div class=\"notification-message\">{{ getNotificationMessage(notification) }}</div>\r\n <div class=\"notification-meta\">\r\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\r\n <span class=\"time\">{{ formatDate(notification.createdAt) }}</span>\r\n </div>\r\n </div>\r\n <button\r\n class=\"read-btn\"\r\n (click)=\"markAsRead(notification.id, $event)\"\r\n title=\"Mark as read\"\r\n *ngIf=\"!notification.isRead\"\r\n >\r\n ✓\r\n </button>\r\n <button\r\n class=\"delete-btn\"\r\n (click)=\"delete(notification.id, $event)\"\r\n title=\"Delete notification\"\r\n *ngIf=\"notification.isRead\"\r\n >\r\n 🗑\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"currentNotifications.length === 0\">\r\n <div class=\"empty-state\">\r\n No {{ activeTab }} notifications\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Footer Actions -->\r\n <div class=\"panel-footer\" *ngIf=\"currentNotifications.length > 0\">\r\n <div class=\"footer-actions\" *ngIf=\"activeTab === 'unread'\">\r\n <button class=\"action-btn\" (click)=\"markAllAsRead()\" *ngIf=\"unreadNotifications.length > 0\">\r\n Mark all as read\r\n </button>\r\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\" *ngIf=\"unreadNotifications.length > 0\">\r\n Delete all\r\n </button>\r\n </div>\r\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\" *ngIf=\"activeTab === 'read' && readNotifications.length > 0\">\r\n Delete all\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Details Modal -->\r\n <div class=\"modal-overlay\" *ngIf=\"selectedNotification\" (click)=\"closeDetails()\">\r\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-header\">\r\n <h3>{{ selectedNotification.title }}</h3>\r\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\">✕</button>\r\n </div>\r\n <div class=\"modal-meta\">\r\n <span class=\"app-name\">{{ selectedNotification.sourceAppName }}</span>\r\n <span class=\"time\">{{ formatDate(selectedNotification.createdAt) }}</span>\r\n </div>\r\n <div class=\"modal-body\" [innerHTML]=\"getHtmlMessage(selectedNotification)\"></div>\r\n <div class=\"modal-footer\">\r\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\r\n </div>\r\n </div>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n display: block;\r\n position: relative;\r\n --primary-color: #1976d2;\r\n --primary-hover: #1565c0;\r\n --success-color: #4caf50;\r\n --error-color: #f44336;\r\n --text-primary: #333;\r\n --text-secondary: #666;\r\n --text-muted: #999;\r\n --bg-primary: white;\r\n --bg-secondary: #f5f5f5;\r\n --bg-tertiary: #fafafa;\r\n --bg-hover: #f5f5f5;\r\n --bg-unread: #e3f2fd;\r\n --border-color: #e0e0e0;\r\n --border-light: #f0f0f0;\r\n --shadow: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n display: block;\r\n position: relative;\r\n --primary-color: #90caf9;\r\n --primary-hover: #64b5f6;\r\n --success-color: #81c784;\r\n --error-color: #ef5350;\r\n --text-primary: #e0e0e0;\r\n --text-secondary: #b0b0b0;\r\n --text-muted: #888;\r\n --bg-primary: #1e1e1e;\r\n --bg-secondary: #2d2d2d;\r\n --bg-tertiary: #252525;\r\n --bg-hover: #333;\r\n --bg-unread: rgba(144, 202, 249, 0.1);\r\n --border-color: #404040;\r\n --border-light: #333;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n }\r\n\r\n .notification-panel {\r\n position: fixed;\r\n top: 0;\r\n right: -350px;\r\n width: 350px;\r\n height: 100vh;\r\n background: var(--bg-primary);\r\n box-shadow: -2px 0 8px var(--shadow);\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 1030;\r\n transition: right 0.3s ease;\r\n }\r\n\r\n .notification-panel.open {\r\n right: 0;\r\n }\r\n\r\n .panel-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .panel-header h3 {\r\n margin: 0;\r\n font-size: 18px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .close-btn {\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n color: var(--text-secondary);\r\n padding: 0;\r\n width: 32px;\r\n height: 32px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n transition: color 0.2s;\r\n }\r\n\r\n .close-btn:hover {\r\n color: var(--text-primary);\r\n }\r\n\r\n .tabs {\r\n display: flex;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .tab-btn {\r\n flex: 1;\r\n padding: 12px 16px;\r\n background: none;\r\n border: none;\r\n color: var(--text-secondary);\r\n cursor: pointer;\r\n font-size: 14px;\r\n font-weight: 500;\r\n transition: all 0.2s;\r\n border-bottom: 2px solid transparent;\r\n }\r\n\r\n .tab-btn:hover {\r\n background-color: var(--bg-hover);\r\n color: var(--text-primary);\r\n }\r\n\r\n .tab-btn.active {\r\n color: var(--primary-color);\r\n border-bottom-color: var(--primary-color);\r\n background-color: var(--bg-primary);\r\n }\r\n\r\n .notifications-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n }\r\n\r\n .notification-item {\r\n display: flex;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--border-light);\r\n cursor: pointer;\r\n background-color: var(--bg-tertiary);\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .notification-item:hover {\r\n background-color: var(--bg-hover);\r\n }\r\n\r\n .notification-item.unread {\r\n background-color: var(--bg-unread);\r\n }\r\n\r\n .notification-content {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n .notification-title {\r\n font-weight: 600;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n .notification-message {\r\n color: var(--text-secondary);\r\n font-size: 12px;\r\n line-height: 1.4;\r\n margin-bottom: 6px;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n }\r\n\r\n .notification-meta {\r\n display: flex;\r\n justify-content: space-between;\r\n font-size: 12px;\r\n color: var(--text-muted);\r\n }\r\n\r\n .app-name {\r\n font-weight: 500;\r\n color: var(--primary-color);\r\n }\r\n\r\n .read-btn {\r\n background: none;\r\n border: none;\r\n color: var(--text-muted);\r\n cursor: pointer;\r\n font-size: 14px;\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .read-btn:hover {\r\n color: var(--success-color);\r\n }\r\n\r\n .delete-btn {\r\n background: none;\r\n border: none;\r\n color: var(--text-muted);\r\n cursor: pointer;\r\n font-size: 14px;\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .delete-btn:hover {\r\n color: var(--error-color);\r\n }\r\n\r\n .empty-state {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: 100%;\r\n color: var(--text-muted);\r\n font-size: 14px;\r\n }\r\n\r\n .panel-footer {\r\n padding: 12px 16px;\r\n border-top: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .footer-actions {\r\n display: flex;\r\n gap: 8px;\r\n }\r\n\r\n .footer-actions .action-btn {\r\n flex: 1;\r\n }\r\n\r\n .action-btn {\r\n width: 100%;\r\n padding: 8px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .action-btn:hover {\r\n background-color: var(--primary-hover);\r\n }\r\n\r\n .delete-all-btn {\r\n background-color: var(--error-color);\r\n color: white;\r\n }\r\n\r\n .delete-all-btn:hover {\r\n background-color: #d32f2f; /* Darker red for hover */\r\n }\r\n\r\n /* Modal Overlay */\r\n .modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n z-index: 1060;\r\n }\r\n\r\n .modal-container {\r\n background: var(--bg-primary);\r\n border-radius: 8px;\r\n width: 90%;\r\n max-width: 600px;\r\n max-height: 80vh;\r\n display: flex;\r\n flex-direction: column;\r\n box-shadow: 0 4px 20px var(--shadow);\r\n }\r\n\r\n .modal-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 20px;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n border-radius: 8px 8px 0 0;\r\n }\r\n\r\n .modal-header h3 {\r\n margin: 0;\r\n font-size: 18px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .modal-meta {\r\n display: flex;\r\n justify-content: space-between;\r\n padding: 8px 20px;\r\n font-size: 12px;\r\n color: var(--text-muted);\r\n background-color: var(--bg-tertiary);\r\n border-bottom: 1px solid var(--border-light);\r\n }\r\n\r\n .modal-body {\r\n padding: 20px;\r\n overflow-y: auto;\r\n flex: 1;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n line-height: 1.6;\r\n }\r\n\r\n .modal-footer {\r\n padding: 12px 20px;\r\n border-top: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n border-radius: 0 0 8px 8px;\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n\r\n .modal-footer .action-btn {\r\n width: auto;\r\n padding: 8px 24px;\r\n }\r\n\r\n @media (max-width: 600px) {\r\n .notification-panel {\r\n width: 100%;\r\n right: -100%;\r\n }\r\n\r\n .modal-container {\r\n width: 95%;\r\n max-height: 90vh;\r\n }\r\n }\r\n `]\r\n})\r\nexport class NotificationPanelComponent implements OnInit, OnDestroy {\r\n @Output() notificationRead = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n isOpen = false;\r\n notifications: NotificationDto[] = [];\r\n currentTheme: Theme = 'light';\r\n activeTab: 'unread' | 'read' = 'unread'; // Default to unread tab\r\n private destroy$ = new Subject<void>();\r\n\r\n get unreadNotifications(): NotificationDto[] {\r\n return this.notifications.filter(n => !n.isRead);\r\n }\r\n\r\n get readNotifications(): NotificationDto[] {\r\n return this.notifications.filter(n => n.isRead);\r\n }\r\n\r\n get currentNotifications(): NotificationDto[] {\r\n return this.activeTab === 'unread' ? this.unreadNotifications : this.readNotifications;\r\n }\r\n\r\n selectedNotification: NotificationDto | null = null;\r\n\r\n // Returns plain text message for list display\r\n getNotificationMessage(notification: NotificationDto): string {\r\n return notification.message || '';\r\n }\r\n\r\n private readonly sanitizer = inject(DomSanitizer);\r\n\r\n // Returns HTML message for modal display (bypasses Angular sanitizer to preserve inline styles)\r\n getHtmlMessage(notification: NotificationDto): SafeHtml {\r\n const html = notification.messageHtml || notification.message || '';\r\n return this.sanitizer.bypassSecurityTrustHtml(html);\r\n }\r\n\r\n constructor(private authService: MesAuthService, private toastService: ToastService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n this.loadNotifications();\r\n\r\n // Listen for new real-time notifications\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe((notification: RealTimeNotificationDto) => {\r\n // Show toast for new notification\r\n this.toastService.show(\r\n notification.messageHtml || notification.message || '',\r\n notification.title,\r\n 'info',\r\n 5000\r\n );\r\n // Reload notifications list\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n private loadNotifications() {\r\n this.authService.getNotifications(1, 50, true).subscribe({ // includeRead = true to get both read and unread\r\n next: (response: PagedList<NotificationDto>) => {\r\n this.notifications = response.items || [];\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n open() {\r\n this.isOpen = true;\r\n this.activeTab = 'unread'; // Reset to unread tab when opening\r\n }\r\n\r\n close() {\r\n this.isOpen = false;\r\n }\r\n\r\n switchTab(tab: 'unread' | 'read') {\r\n this.activeTab = tab;\r\n }\r\n\r\n openDetails(notification: NotificationDto) {\r\n this.selectedNotification = notification;\r\n // Mark as read when opening details (if not already read)\r\n if (!notification.isRead) {\r\n this.authService.markAsRead(notification.id).subscribe({\r\n next: () => {\r\n notification.isRead = true;\r\n this.notificationRead.emit();\r\n },\r\n error: () => {}\r\n });\r\n }\r\n }\r\n\r\n closeDetails() {\r\n this.selectedNotification = null;\r\n }\r\n\r\n markAsRead(notificationId: string, event?: Event) {\r\n if (event) {\r\n event.stopPropagation();\r\n }\r\n this.authService.markAsRead(notificationId).subscribe({\r\n next: () => {\r\n const notification = this.notifications.find(n => n.id === notificationId);\r\n if (notification) {\r\n notification.isRead = true;\r\n this.notificationRead.emit();\r\n }\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n markAllAsRead() {\r\n this.authService.markAllAsRead().subscribe({\r\n next: () => {\r\n this.notifications.forEach(n => n.isRead = true);\r\n this.notificationRead.emit();\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n deleteAllRead() {\r\n const readNotificationIds = this.notifications\r\n .filter(n => n.isRead)\r\n .map(n => n.id);\r\n\r\n // Delete all read notifications\r\n const deletePromises = readNotificationIds.map(id =>\r\n this.authService.deleteNotification(id).toPromise()\r\n );\r\n\r\n Promise.all(deletePromises).then(() => {\r\n // Remove all read notifications from the local array\r\n this.notifications = this.notifications.filter(n => !n.isRead);\r\n }).catch((err) => {\r\n // If bulk delete fails, reload notifications to get current state\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n deleteAllUnread() {\r\n const unreadNotificationIds = this.notifications\r\n .filter(n => !n.isRead)\r\n .map(n => n.id);\r\n\r\n // Delete all unread notifications\r\n const deletePromises = unreadNotificationIds.map(id =>\r\n this.authService.deleteNotification(id).toPromise()\r\n );\r\n\r\n Promise.all(deletePromises).then(() => {\r\n // Remove all unread notifications from the local array\r\n this.notifications = this.notifications.filter(n => n.isRead);\r\n }).catch((err) => {\r\n // If bulk delete fails, reload notifications to get current state\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n delete(notificationId: string, event: Event) {\r\n event.stopPropagation();\r\n this.authService.deleteNotification(notificationId).subscribe({\r\n next: () => {\r\n this.notifications = this.notifications.filter(n => n.id !== notificationId);\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n formatDate(dateString: string): string {\r\n // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)\r\n const normalizedDateString = this.parseUtcDate(dateString);\r\n const date = new Date(normalizedDateString);\r\n \r\n // Check if the date is valid\r\n if (isNaN(date.getTime())) {\r\n return 'Invalid date';\r\n }\r\n \r\n const now = new Date();\r\n const diffMs = now.getTime() - date.getTime();\r\n const diffMins = Math.floor(diffMs / 60000);\r\n const diffHours = Math.floor(diffMs / 3600000);\r\n const diffDays = Math.floor(diffMs / 86400000);\r\n\r\n if (diffMins < 1) return 'Now';\r\n if (diffMins < 60) return `${diffMins}m ago`;\r\n if (diffHours < 24) return `${diffHours}h ago`;\r\n if (diffDays < 7) return `${diffDays}d ago`;\r\n \r\n return date.toLocaleDateString();\r\n }\r\n\r\n // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)\r\n private parseUtcDate(dateStr: string): string {\r\n // Handle date strings that might be missing the 'T' separator\r\n // Convert formats like \"2023-12-01 12:30:45\" to \"2023-12-01T12:30:45\"\r\n let normalized = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T');\r\n \r\n // If no timezone indicator, assume UTC by appending 'Z'\r\n if (!normalized.endsWith('Z') && !normalized.includes('+') && !normalized.includes('-', 10)) {\r\n normalized += 'Z';\r\n }\r\n \r\n return normalized;\r\n }\r\n}\r\n","import { Component, ViewChild, AfterViewInit } from '@angular/core';\r\nimport { ToastContainerComponent } from './toast-container.component';\r\nimport { UserProfileComponent } from './user-profile.component';\r\nimport { NotificationPanelComponent } from './notification-panel.component';\r\n\r\n@Component({\r\n selector: 'ma-user',\r\n standalone: true,\r\n imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent],\r\n template: `\r\n <ma-toast-container></ma-toast-container>\r\n <div class=\"user-header\">\r\n <ma-user-profile (notificationClick)=\"notificationPanel.open()\"></ma-user-profile>\r\n </div>\r\n <ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\r\n `,\r\n styles: [`\r\n .user-header {\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n `]\r\n})\r\nexport class MaUserComponent implements AfterViewInit {\r\n @ViewChild(UserProfileComponent) userProfile?: UserProfileComponent;\r\n\r\n ngAfterViewInit() {\r\n // Ensure proper initialization\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n\r\n onNotificationRead() {\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { MesAuthService } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-notification-badge',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n `,\r\n styles: [`\r\n :host {\r\n --error-color: #f44336;\r\n }\r\n\r\n :host(.theme-dark) {\r\n --error-color: #ef5350;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n `]\r\n})\r\nexport class NotificationBadgeComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n \r\n unreadCount = 0;\r\n currentTheme: Theme = 'light';\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private authService: MesAuthService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.hasUser = !!user;\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n this.loadUnreadCount();\r\n });\r\n \r\n // Listen for new notifications\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n private loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => console.error('Error loading unread count:', err)\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.MesAuthService","i3.ThemeService","i1.ToastService","i2.ThemeService","i2.ToastService"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { InjectionToken, EnvironmentProviders, OnDestroy, OnInit, EventEmitter,
3
3
  import { HttpClient, HttpInterceptorFn } from '@angular/common/http';
4
4
  import { Observable } from 'rxjs';
5
5
  import { Router } from '@angular/router';
6
+ import { SafeHtml } from '@angular/platform-browser';
6
7
 
7
8
  interface MesAuthConfig {
8
9
  apiBaseUrl: string;
@@ -350,7 +351,8 @@ declare class NotificationPanelComponent implements OnInit, OnDestroy {
350
351
  get currentNotifications(): NotificationDto[];
351
352
  selectedNotification: NotificationDto | null;
352
353
  getNotificationMessage(notification: NotificationDto): string;
353
- getHtmlMessage(notification: NotificationDto): string;
354
+ private readonly sanitizer;
355
+ getHtmlMessage(notification: NotificationDto): SafeHtml;
354
356
  constructor(authService: MesAuthService, toastService: ToastService, themeService: ThemeService);
355
357
  ngOnInit(): void;
356
358
  ngOnDestroy(): void;
@@ -366,6 +368,7 @@ declare class NotificationPanelComponent implements OnInit, OnDestroy {
366
368
  deleteAllUnread(): void;
367
369
  delete(notificationId: string, event: Event): void;
368
370
  formatDate(dateString: string): string;
371
+ private parseUtcDate;
369
372
  static ɵfac: i0.ɵɵFactoryDeclaration<NotificationPanelComponent, never>;
370
373
  static ɵcmp: i0.ɵɵComponentDeclaration<NotificationPanelComponent, "ma-notification-panel", never, {}, { "notificationRead": "notificationRead"; }, never, never, true, never>;
371
374
  }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,8 @@
1
+ {
2
+ "$schema": "./node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "dist",
4
+ "allowedNonPeerDependencies": ["@microsoft/signalr", "rxjs"],
5
+ "lib": {
6
+ "entryFile": "src/index.ts"
7
+ }
8
+ }