mesauth-angular 0.2.15 → 0.2.26

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.
Files changed (32) hide show
  1. package/README.md +139 -61
  2. package/dist/README.md +139 -61
  3. package/dist/{fesm2020 → fesm2022}/mesauth-angular.mjs +796 -691
  4. package/dist/fesm2022/mesauth-angular.mjs.map +1 -0
  5. package/dist/index.d.ts +355 -9
  6. package/package.json +12 -12
  7. package/dist/esm2020/index.mjs +0 -10
  8. package/dist/esm2020/ma-user.component.mjs +0 -32
  9. package/dist/esm2020/mes-auth.interceptor.mjs +0 -29
  10. package/dist/esm2020/mes-auth.module.mjs +0 -23
  11. package/dist/esm2020/mes-auth.service.mjs +0 -175
  12. package/dist/esm2020/mesauth-angular.mjs +0 -5
  13. package/dist/esm2020/notification-badge.component.mjs +0 -71
  14. package/dist/esm2020/notification-panel.component.mjs +0 -333
  15. package/dist/esm2020/theme.service.mjs +0 -63
  16. package/dist/esm2020/toast-container.component.mjs +0 -83
  17. package/dist/esm2020/toast.service.mjs +0 -41
  18. package/dist/esm2020/user-profile.component.mjs +0 -223
  19. package/dist/fesm2015/mesauth-angular.mjs +0 -1042
  20. package/dist/fesm2015/mesauth-angular.mjs.map +0 -1
  21. package/dist/fesm2020/mesauth-angular.mjs.map +0 -1
  22. package/dist/ma-user.component.d.ts +0 -8
  23. package/dist/mes-auth.interceptor.d.ts +0 -13
  24. package/dist/mes-auth.module.d.ts +0 -6
  25. package/dist/mes-auth.service.d.ts +0 -121
  26. package/dist/notification-badge.component.d.ts +0 -20
  27. package/dist/notification-panel.component.d.ts +0 -36
  28. package/dist/package.json +0 -52
  29. package/dist/theme.service.d.ts +0 -19
  30. package/dist/toast-container.component.d.ts +0 -18
  31. package/dist/toast.service.d.ts +0 -18
  32. package/dist/user-profile.component.d.ts +0 -31
@@ -1,1042 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { Injectable, Optional, NgModule, EventEmitter, Component, Output, HostBinding, HostListener, ViewChild } from '@angular/core';
3
- import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
4
- import { BehaviorSubject, Subject, throwError } from 'rxjs';
5
- import { filter, debounceTime, tap, catchError, takeUntil } from 'rxjs/operators';
6
- import * as i2 from '@angular/router';
7
- import { NavigationEnd } from '@angular/router';
8
- import * as i1 from '@angular/common/http';
9
- import { HTTP_INTERCEPTORS } from '@angular/common/http';
10
- import * as i3 from '@angular/common';
11
- import { NgIf, CommonModule, NgFor } from '@angular/common';
12
-
13
- var NotificationType;
14
- (function (NotificationType) {
15
- NotificationType["Info"] = "Info";
16
- NotificationType["Warning"] = "Warning";
17
- NotificationType["Error"] = "Error";
18
- NotificationType["Success"] = "Success";
19
- })(NotificationType || (NotificationType = {}));
20
- class MesAuthService {
21
- constructor(http, router) {
22
- this.http = http;
23
- this.router = router;
24
- this.hubConnection = null;
25
- this._currentUser = new BehaviorSubject(null);
26
- this.currentUser$ = this._currentUser.asObservable();
27
- this._notifications = new Subject();
28
- this.notifications$ = this._notifications.asObservable();
29
- this._feNav = new BehaviorSubject(null);
30
- this.feNav$ = this._feNav.asObservable();
31
- this.apiBase = '';
32
- this.config = null;
33
- // Listen for route changes - only refresh user data if needed for SPA navigation
34
- // This helps maintain authentication state in single-page applications
35
- if (this.router) {
36
- this.router.events
37
- .pipe(filter(event => event instanceof NavigationEnd), debounceTime(1000) // Longer debounce to avoid interfering with login flow
38
- )
39
- .subscribe((event) => {
40
- // Only refresh if user is logged in and navigating to protected routes
41
- // Avoid refreshing during login/logout flows
42
- if (this._currentUser.value && this.isProtectedRoute(event.url)) {
43
- // Small delay to ensure any login/logout operations complete
44
- setTimeout(() => {
45
- if (this._currentUser.value) {
46
- this.refreshUser();
47
- }
48
- }, 100);
49
- }
50
- });
51
- }
52
- }
53
- isProtectedRoute(url) {
54
- // Consider routes protected if they don't include auth-related paths
55
- return !url.includes('/login') && !url.includes('/auth') && !url.includes('/signin') && !url.includes('/logout');
56
- }
57
- init(config) {
58
- this.config = config;
59
- this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
60
- this.fetchCurrentUser();
61
- this.fetchInitialNotifications();
62
- }
63
- getConfig() {
64
- return this.config;
65
- }
66
- fetchCurrentUser() {
67
- var _a, _b;
68
- if (!this.apiBase)
69
- return;
70
- const url = `${this.apiBase}/auth/me`;
71
- this.http.get(url, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true }).subscribe({
72
- next: (u) => {
73
- this._currentUser.next(u);
74
- if (u && this.config) {
75
- this.startConnection(this.config);
76
- this.fetchFeNav();
77
- }
78
- else {
79
- this._feNav.next(null);
80
- }
81
- },
82
- error: (err) => {
83
- this._currentUser.next(null);
84
- this._feNav.next(null);
85
- }
86
- });
87
- }
88
- fetchInitialNotifications() {
89
- var _a, _b;
90
- if (!this.apiBase)
91
- return;
92
- this.http.get(`${this.apiBase}/notif/me`, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true }).subscribe({
93
- next: (notifications) => {
94
- if (Array.isArray(notifications === null || notifications === void 0 ? void 0 : notifications.items)) {
95
- notifications.items.forEach((n) => this._notifications.next(n));
96
- }
97
- },
98
- error: (err) => { }
99
- });
100
- }
101
- fetchFeNav() {
102
- var _a, _b, _c;
103
- if (!this.apiBase || !((_a = this._currentUser.value) === null || _a === void 0 ? void 0 : _a.userId))
104
- return;
105
- const url = `${this.apiBase}/fenavs/${this._currentUser.value.userId}`;
106
- this.http.get(url, { withCredentials: (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.withCredentials) !== null && _c !== void 0 ? _c : true }).subscribe({
107
- next: (response) => {
108
- if (response === null || response === void 0 ? void 0 : response.content) {
109
- const feNavData = {
110
- items: response.content,
111
- lastUpdated: new Date().toISOString()
112
- };
113
- this._feNav.next(feNavData);
114
- }
115
- },
116
- error: (err) => {
117
- this._feNav.next(null);
118
- }
119
- });
120
- }
121
- getUnreadCount() {
122
- var _a, _b;
123
- return this.http.get(`${this.apiBase}/notif/me/unread-count`, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true });
124
- }
125
- getNotifications(page = 1, pageSize = 20, includeRead = false, type) {
126
- var _a, _b;
127
- let url = `${this.apiBase}/notif/me?page=${page}&pageSize=${pageSize}&includeRead=${includeRead}`;
128
- if (type) {
129
- url += `&type=${type}`;
130
- }
131
- return this.http.get(url, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true });
132
- }
133
- markAsRead(notificationId) {
134
- var _a, _b;
135
- return this.http.patch(`${this.apiBase}/notif/${notificationId}/read`, {}, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true });
136
- }
137
- markAllAsRead() {
138
- var _a, _b;
139
- return this.http.patch(`${this.apiBase}/notif/me/read-all`, {}, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true });
140
- }
141
- deleteNotification(notificationId) {
142
- var _a, _b;
143
- return this.http.delete(`${this.apiBase}/notif/${notificationId}`, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true });
144
- }
145
- startConnection(config) {
146
- var _a;
147
- if (this.hubConnection)
148
- return;
149
- const signalrUrl = config.apiBaseUrl.replace(/\/$/, '') + '/hub/notification';
150
- const builder = new HubConnectionBuilder()
151
- .withUrl(signalrUrl, { withCredentials: (_a = config.withCredentials) !== null && _a !== void 0 ? _a : true })
152
- .withAutomaticReconnect()
153
- .configureLogging(LogLevel.Warning);
154
- this.hubConnection = builder.build();
155
- this.hubConnection.on('ReceiveNotification', (n) => {
156
- this._notifications.next(n);
157
- });
158
- this.hubConnection.start().then(() => { }).catch((err) => { });
159
- this.hubConnection.onclose(() => { });
160
- this.hubConnection.onreconnecting(() => { });
161
- this.hubConnection.onreconnected(() => { });
162
- }
163
- stop() {
164
- if (!this.hubConnection)
165
- return;
166
- this.hubConnection.stop().catch(() => { });
167
- this.hubConnection = null;
168
- }
169
- logout() {
170
- var _a, _b;
171
- const url = `${this.apiBase}/auth/logout`;
172
- return this.http.post(url, {}, { withCredentials: (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.withCredentials) !== null && _b !== void 0 ? _b : true }).pipe(tap(() => {
173
- this._currentUser.next(null);
174
- this._feNav.next(null);
175
- this.stop();
176
- }));
177
- }
178
- refreshUser() {
179
- this.fetchCurrentUser();
180
- }
181
- }
182
- MesAuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthService, deps: [{ token: i1.HttpClient }, { token: i2.Router, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
183
- MesAuthService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthService });
184
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthService, decorators: [{
185
- type: Injectable
186
- }], ctorParameters: function () {
187
- return [{ type: i1.HttpClient }, { type: i2.Router, decorators: [{
188
- type: Optional
189
- }] }];
190
- } });
191
-
192
- class MesAuthInterceptor {
193
- constructor(authService, router) {
194
- this.authService = authService;
195
- this.router = router;
196
- }
197
- intercept(req, next) {
198
- return next.handle(req).pipe(catchError((error) => {
199
- if (error.status === 403) {
200
- const config = this.authService.getConfig();
201
- const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
202
- const returnUrl = encodeURIComponent(window.location.href);
203
- window.location.href = `${baseUrl}/403?returnUrl=${returnUrl}`;
204
- }
205
- return throwError(error);
206
- }));
207
- }
208
- }
209
- MesAuthInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthInterceptor, deps: [{ token: MesAuthService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Injectable });
210
- MesAuthInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthInterceptor });
211
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthInterceptor, decorators: [{
212
- type: Injectable
213
- }], ctorParameters: function () { return [{ type: MesAuthService }, { type: i2.Router }]; } });
214
-
215
- class MesAuthModule {
216
- }
217
- MesAuthModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
218
- MesAuthModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: MesAuthModule });
219
- MesAuthModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthModule, providers: [
220
- MesAuthService,
221
- { provide: HTTP_INTERCEPTORS, useClass: MesAuthInterceptor, multi: true }
222
- ] });
223
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MesAuthModule, decorators: [{
224
- type: NgModule,
225
- args: [{
226
- providers: [
227
- MesAuthService,
228
- { provide: HTTP_INTERCEPTORS, useClass: MesAuthInterceptor, multi: true }
229
- ]
230
- }]
231
- }] });
232
-
233
- class ThemeService {
234
- constructor() {
235
- this._currentTheme = new BehaviorSubject('light');
236
- this.currentTheme$ = this._currentTheme.asObservable();
237
- this.observer = null;
238
- this.detectTheme();
239
- this.startWatching();
240
- }
241
- ngOnDestroy() {
242
- this.stopWatching();
243
- }
244
- detectTheme() {
245
- const html = document.documentElement;
246
- const isDark = html.classList.contains('dark') ||
247
- html.getAttribute('data-theme') === 'dark' ||
248
- html.getAttribute('theme') === 'dark' ||
249
- html.getAttribute('data-coreui-theme') === 'dark';
250
- this._currentTheme.next(isDark ? 'dark' : 'light');
251
- }
252
- startWatching() {
253
- if (typeof MutationObserver === 'undefined') {
254
- // Fallback for older browsers - check periodically
255
- setInterval(() => this.detectTheme(), 1000);
256
- return;
257
- }
258
- this.observer = new MutationObserver(() => {
259
- this.detectTheme();
260
- });
261
- this.observer.observe(document.documentElement, {
262
- attributes: true,
263
- attributeFilter: ['class', 'data-theme', 'theme', 'data-coreui-theme']
264
- });
265
- }
266
- stopWatching() {
267
- if (this.observer) {
268
- this.observer.disconnect();
269
- this.observer = null;
270
- }
271
- }
272
- get currentTheme() {
273
- return this._currentTheme.value;
274
- }
275
- // Method to manually set theme if needed
276
- setTheme(theme) {
277
- this._currentTheme.next(theme);
278
- }
279
- // Re-detect theme from DOM
280
- refreshTheme() {
281
- this.detectTheme();
282
- }
283
- }
284
- ThemeService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
285
- ThemeService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ThemeService, providedIn: 'root' });
286
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ThemeService, decorators: [{
287
- type: Injectable,
288
- args: [{
289
- providedIn: 'root'
290
- }]
291
- }], ctorParameters: function () { return []; } });
292
-
293
- class UserProfileComponent {
294
- constructor(authService, router, themeService) {
295
- this.authService = authService;
296
- this.router = router;
297
- this.themeService = themeService;
298
- this.notificationClick = new EventEmitter();
299
- this.currentUser = null;
300
- this.currentTheme = 'light';
301
- this.unreadCount = 0;
302
- this.dropdownOpen = false;
303
- this.destroy$ = new Subject();
304
- }
305
- get themeClass() {
306
- return `theme-${this.currentTheme}`;
307
- }
308
- ngOnInit() {
309
- this.authService.currentUser$
310
- .pipe(takeUntil(this.destroy$))
311
- .subscribe(user => {
312
- this.currentUser = user;
313
- });
314
- this.themeService.currentTheme$
315
- .pipe(takeUntil(this.destroy$))
316
- .subscribe(theme => {
317
- this.currentTheme = theme;
318
- });
319
- this.loadUnreadCount();
320
- // Listen for new notifications
321
- this.authService.notifications$
322
- .pipe(takeUntil(this.destroy$))
323
- .subscribe(() => {
324
- console.log('Notification received, updating unread count');
325
- this.loadUnreadCount();
326
- });
327
- }
328
- ngOnDestroy() {
329
- this.destroy$.next();
330
- this.destroy$.complete();
331
- }
332
- loadUnreadCount() {
333
- this.authService.getUnreadCount().subscribe({
334
- next: (response) => {
335
- this.unreadCount = response.unreadCount || 0;
336
- },
337
- error: (err) => { }
338
- });
339
- }
340
- getAvatarUrl(user) {
341
- const config = this.authService.getConfig();
342
- const baseUrl = (config === null || config === void 0 ? void 0 : config.apiBaseUrl) || '';
343
- // Use userId for the avatar endpoint
344
- const userId = user.userId;
345
- if (userId && baseUrl) {
346
- return `${baseUrl.replace(/\/$/, '')}/auth/${userId}/avatar`;
347
- }
348
- // Fallback to UI avatars service if no userId or baseUrl
349
- const displayName = user.userName || user.userId || 'User';
350
- return `https://ui-avatars.com/api/?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;
351
- }
352
- getLastNameInitial(user) {
353
- const fullName = user.fullName || user.userName || 'U';
354
- const parts = fullName.split(' ');
355
- const lastPart = parts[parts.length - 1];
356
- return lastPart.charAt(0).toUpperCase();
357
- }
358
- toggleDropdown() {
359
- this.dropdownOpen = !this.dropdownOpen;
360
- }
361
- onDocumentClick(event) {
362
- const target = event.target;
363
- const clickedInside = target.closest('.user-menu-wrapper');
364
- if (!clickedInside) {
365
- this.dropdownOpen = false;
366
- }
367
- }
368
- onLogin() {
369
- const config = this.authService.getConfig();
370
- const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
371
- const returnUrl = encodeURIComponent(this.router.url);
372
- window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
373
- }
374
- onViewProfile() {
375
- const config = this.authService.getConfig();
376
- const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
377
- window.location.href = `${baseUrl}/profile`;
378
- this.dropdownOpen = false;
379
- }
380
- onLogout() {
381
- this.authService.logout().subscribe({
382
- next: () => {
383
- // Clear current user after successful logout
384
- this.dropdownOpen = false;
385
- // Navigate to login with return URL
386
- const config = this.authService.getConfig();
387
- const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
388
- const returnUrl = encodeURIComponent(window.location.href);
389
- window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
390
- },
391
- error: (err) => {
392
- // Still navigate to login even if logout fails
393
- const config = this.authService.getConfig();
394
- const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
395
- window.location.href = `${baseUrl}/login`;
396
- }
397
- });
398
- }
399
- onNotificationClick() {
400
- this.notificationClick.emit();
401
- }
402
- }
403
- UserProfileComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
404
- UserProfileComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", outputs: { notificationClick: "notificationClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
405
- <div class="user-profile-container">
406
- <!-- Not logged in -->
407
- <ng-container *ngIf="!currentUser">
408
- <button class="login-btn" (click)="onLogin()">
409
- Login
410
- </button>
411
- </ng-container>
412
-
413
- <!-- Logged in -->
414
- <ng-container *ngIf="currentUser">
415
- <div class="user-header">
416
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
417
- <span class="icon">🔔</span>
418
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
419
- </button>
420
-
421
- <div class="user-menu-wrapper">
422
- <button class="user-menu-btn" (click)="toggleDropdown()">
423
- <img
424
- *ngIf="currentUser.fullName || currentUser.userName"
425
- [src]="getAvatarUrl(currentUser)"
426
- [alt]="currentUser.fullName || currentUser.userName"
427
- class="avatar"
428
- />
429
- <span *ngIf="!(currentUser.fullName || currentUser.userName)" class="avatar-initial">
430
- {{ getLastNameInitial(currentUser) }}
431
- </span>
432
- </button>
433
-
434
- <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
435
- <div class="mes-dropdown-header">
436
- {{ currentUser.fullName || currentUser.userName }}
437
- </div>
438
- <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
439
- View Profile
440
- </button>
441
- <button class="mes-dropdown-item logout-item" (click)="onLogout()">
442
- Logout
443
- </button>
444
- </div>
445
- </div>
446
- </div>
447
- </ng-container>
448
- </div>
449
- `, isInline: true, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--error-color: #f44336;--error-light: #ffebee;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .15);--shadow-light: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--error-color: #ef5350;--error-light: rgba(239, 83, 80, .1);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3);--shadow-light: rgba(0, 0, 0, .2)}.user-profile-container{display:flex;align-items:center;gap:16px;padding:0 16px}.login-btn{padding:8px 16px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .3s}.login-btn:hover{background-color:var(--primary-hover)}.user-header{display:flex;align-items:center;gap:16px}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background-color .2s;display:flex;align-items:center;justify-content:center}.user-menu-btn:hover{background-color:var(--primary-light)}.avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:#e0e0e0}.avatar-initial{width:40px;height:40px;border-radius:50%;background-color:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px}.mes-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:4px;box-shadow:0 2px 8px var(--shadow);min-width:200px;z-index:1000;overflow:hidden}.mes-dropdown-header{padding:12px 16px;border-bottom:1px solid var(--border-light);font-weight:600;color:var(--text-primary);font-size:14px}.mes-dropdown-item{display:block;width:100%;padding:12px 16px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-primary);text-decoration:none;transition:background-color .2s}.mes-dropdown-item:hover{background-color:var(--bg-hover)}.profile-link{color:var(--primary-color)}.logout-item{border-top:1px solid var(--border-light);color:var(--error-color)}.logout-item:hover{background-color:var(--error-light)}.user-info{display:flex;flex-direction:column;gap:2px}.user-name{font-weight:500;font-size:14px;color:var(--text-primary)}.user-position{font-size:12px;color:var(--text-secondary)}.logout-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;transition:color .2s}.logout-btn:hover{color:var(--primary-color)}@media (max-width: 768px){.user-info{display:none}.avatar{width:32px;height:32px}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
450
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: UserProfileComponent, decorators: [{
451
- type: Component,
452
- args: [{ selector: 'ma-user-profile', standalone: true, imports: [NgIf], template: `
453
- <div class="user-profile-container">
454
- <!-- Not logged in -->
455
- <ng-container *ngIf="!currentUser">
456
- <button class="login-btn" (click)="onLogin()">
457
- Login
458
- </button>
459
- </ng-container>
460
-
461
- <!-- Logged in -->
462
- <ng-container *ngIf="currentUser">
463
- <div class="user-header">
464
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
465
- <span class="icon">🔔</span>
466
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
467
- </button>
468
-
469
- <div class="user-menu-wrapper">
470
- <button class="user-menu-btn" (click)="toggleDropdown()">
471
- <img
472
- *ngIf="currentUser.fullName || currentUser.userName"
473
- [src]="getAvatarUrl(currentUser)"
474
- [alt]="currentUser.fullName || currentUser.userName"
475
- class="avatar"
476
- />
477
- <span *ngIf="!(currentUser.fullName || currentUser.userName)" class="avatar-initial">
478
- {{ getLastNameInitial(currentUser) }}
479
- </span>
480
- </button>
481
-
482
- <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
483
- <div class="mes-dropdown-header">
484
- {{ currentUser.fullName || currentUser.userName }}
485
- </div>
486
- <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
487
- View Profile
488
- </button>
489
- <button class="mes-dropdown-item logout-item" (click)="onLogout()">
490
- Logout
491
- </button>
492
- </div>
493
- </div>
494
- </div>
495
- </ng-container>
496
- </div>
497
- `, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--error-color: #f44336;--error-light: #ffebee;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .15);--shadow-light: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--error-color: #ef5350;--error-light: rgba(239, 83, 80, .1);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3);--shadow-light: rgba(0, 0, 0, .2)}.user-profile-container{display:flex;align-items:center;gap:16px;padding:0 16px}.login-btn{padding:8px 16px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .3s}.login-btn:hover{background-color:var(--primary-hover)}.user-header{display:flex;align-items:center;gap:16px}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background-color .2s;display:flex;align-items:center;justify-content:center}.user-menu-btn:hover{background-color:var(--primary-light)}.avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:#e0e0e0}.avatar-initial{width:40px;height:40px;border-radius:50%;background-color:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px}.mes-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:4px;box-shadow:0 2px 8px var(--shadow);min-width:200px;z-index:1000;overflow:hidden}.mes-dropdown-header{padding:12px 16px;border-bottom:1px solid var(--border-light);font-weight:600;color:var(--text-primary);font-size:14px}.mes-dropdown-item{display:block;width:100%;padding:12px 16px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-primary);text-decoration:none;transition:background-color .2s}.mes-dropdown-item:hover{background-color:var(--bg-hover)}.profile-link{color:var(--primary-color)}.logout-item{border-top:1px solid var(--border-light);color:var(--error-color)}.logout-item:hover{background-color:var(--error-light)}.user-info{display:flex;flex-direction:column;gap:2px}.user-name{font-weight:500;font-size:14px;color:var(--text-primary)}.user-position{font-size:12px;color:var(--text-secondary)}.logout-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;transition:color .2s}.logout-btn:hover{color:var(--primary-color)}@media (max-width: 768px){.user-info{display:none}.avatar{width:32px;height:32px}}\n"] }]
498
- }], ctorParameters: function () { return [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }]; }, propDecorators: { notificationClick: [{
499
- type: Output
500
- }], themeClass: [{
501
- type: HostBinding,
502
- args: ['class']
503
- }], onDocumentClick: [{
504
- type: HostListener,
505
- args: ['document:click', ['$event']]
506
- }] } });
507
-
508
- class ToastService {
509
- constructor() {
510
- this.toasts$ = new BehaviorSubject([]);
511
- this.toasts = this.toasts$.asObservable();
512
- }
513
- show(message, title, type = 'info', duration = 5000) {
514
- const id = Math.random().toString(36).substr(2, 9);
515
- const toast = {
516
- id,
517
- message,
518
- title,
519
- type,
520
- duration
521
- };
522
- const currentToasts = this.toasts$.value;
523
- this.toasts$.next([...currentToasts, toast]);
524
- if (duration > 0) {
525
- setTimeout(() => {
526
- this.remove(id);
527
- }, duration);
528
- }
529
- return id;
530
- }
531
- remove(id) {
532
- const currentToasts = this.toasts$.value;
533
- this.toasts$.next(currentToasts.filter(t => t.id !== id));
534
- }
535
- clear() {
536
- this.toasts$.next([]);
537
- }
538
- }
539
- ToastService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
540
- ToastService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ToastService, providedIn: 'root' });
541
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ToastService, decorators: [{
542
- type: Injectable,
543
- args: [{ providedIn: 'root' }]
544
- }] });
545
-
546
- class ToastContainerComponent {
547
- constructor(toastService, themeService) {
548
- this.toastService = toastService;
549
- this.themeService = themeService;
550
- this.toasts = [];
551
- this.currentTheme = 'light';
552
- this.destroy$ = new Subject();
553
- }
554
- get themeClass() {
555
- return `theme-${this.currentTheme}`;
556
- }
557
- ngOnInit() {
558
- this.toastService.toasts
559
- .pipe(takeUntil(this.destroy$))
560
- .subscribe(toasts => {
561
- this.toasts = toasts;
562
- });
563
- this.themeService.currentTheme$
564
- .pipe(takeUntil(this.destroy$))
565
- .subscribe(theme => {
566
- this.currentTheme = theme;
567
- });
568
- }
569
- ngOnDestroy() {
570
- this.destroy$.next();
571
- this.destroy$.complete();
572
- }
573
- close(id) {
574
- this.toastService.remove(id);
575
- }
576
- }
577
- ToastContainerComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ToastContainerComponent, deps: [{ token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
578
- ToastContainerComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: ToastContainerComponent, isStandalone: true, selector: "ma-toast-container", host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
579
- <div class="toast-container">
580
- <div
581
- *ngFor="let toast of toasts"
582
- class="toast"
583
- [class]="'toast-' + toast.type"
584
- [@slideIn]
585
- >
586
- <div class="toast-content">
587
- <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
588
- <div class="toast-message" [innerHTML]="toast.message"></div>
589
- </div>
590
- <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
591
-
592
- </button>
593
- </div>
594
- </div>
595
- `, isInline: true, styles: [":host{--info-color: #2196f3;--success-color: #4caf50;--warning-color: #ff9800;--error-color: #f44336;--text-primary: #333;--bg-primary: white;--shadow: rgba(0, 0, 0, .15);--text-secondary: #999;--border-color: rgba(0, 0, 0, .1)}:host(.theme-dark){--info-color: #64b5f6;--success-color: #81c784;--warning-color: #ffb74d;--error-color: #ef5350;--text-primary: #e0e0e0;--bg-primary: #1e1e1e;--shadow: rgba(0, 0, 0, .3);--text-secondary: #888;--border-color: rgba(255, 255, 255, .1)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none}.toast{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;margin-bottom:12px;border-radius:4px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 4px 12px var(--shadow);pointer-events:auto;min-width:280px;max-width:400px;animation:slideIn .3s ease-out}.toast-content{flex:1}.toast-title{font-weight:600;font-size:14px;margin-bottom:4px}.toast-message{font-size:13px;line-height:1.4}.toast-close{background:none;border:none;cursor:pointer;font-size:18px;color:var(--text-secondary);padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.toast-close:hover{color:var(--text-primary)}.toast-info{border-left:4px solid var(--info-color)}.toast-info .toast-title{color:var(--info-color)}.toast-info .toast-message{color:var(--text-primary)}.toast-success{border-left:4px solid var(--success-color)}.toast-success .toast-title{color:var(--success-color)}.toast-success .toast-message{color:var(--text-primary)}.toast-warning{border-left:4px solid var(--warning-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-warning .toast-message{color:var(--text-primary)}.toast-error{border-left:4px solid var(--error-color)}.toast-error .toast-title{color:var(--error-color)}.toast-error .toast-message{color:var(--text-primary)}@keyframes slideIn{0%{transform:translate(400px);opacity:0}to{transform:translate(0);opacity:1}}@media (max-width: 600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
596
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ToastContainerComponent, decorators: [{
597
- type: Component,
598
- args: [{ selector: 'ma-toast-container', standalone: true, imports: [CommonModule], template: `
599
- <div class="toast-container">
600
- <div
601
- *ngFor="let toast of toasts"
602
- class="toast"
603
- [class]="'toast-' + toast.type"
604
- [@slideIn]
605
- >
606
- <div class="toast-content">
607
- <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
608
- <div class="toast-message" [innerHTML]="toast.message"></div>
609
- </div>
610
- <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
611
-
612
- </button>
613
- </div>
614
- </div>
615
- `, styles: [":host{--info-color: #2196f3;--success-color: #4caf50;--warning-color: #ff9800;--error-color: #f44336;--text-primary: #333;--bg-primary: white;--shadow: rgba(0, 0, 0, .15);--text-secondary: #999;--border-color: rgba(0, 0, 0, .1)}:host(.theme-dark){--info-color: #64b5f6;--success-color: #81c784;--warning-color: #ffb74d;--error-color: #ef5350;--text-primary: #e0e0e0;--bg-primary: #1e1e1e;--shadow: rgba(0, 0, 0, .3);--text-secondary: #888;--border-color: rgba(255, 255, 255, .1)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none}.toast{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;margin-bottom:12px;border-radius:4px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 4px 12px var(--shadow);pointer-events:auto;min-width:280px;max-width:400px;animation:slideIn .3s ease-out}.toast-content{flex:1}.toast-title{font-weight:600;font-size:14px;margin-bottom:4px}.toast-message{font-size:13px;line-height:1.4}.toast-close{background:none;border:none;cursor:pointer;font-size:18px;color:var(--text-secondary);padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.toast-close:hover{color:var(--text-primary)}.toast-info{border-left:4px solid var(--info-color)}.toast-info .toast-title{color:var(--info-color)}.toast-info .toast-message{color:var(--text-primary)}.toast-success{border-left:4px solid var(--success-color)}.toast-success .toast-title{color:var(--success-color)}.toast-success .toast-message{color:var(--text-primary)}.toast-warning{border-left:4px solid var(--warning-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-warning .toast-message{color:var(--text-primary)}.toast-error{border-left:4px solid var(--error-color)}.toast-error .toast-title{color:var(--error-color)}.toast-error .toast-message{color:var(--text-primary)}@keyframes slideIn{0%{transform:translate(400px);opacity:0}to{transform:translate(0);opacity:1}}@media (max-width: 600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"] }]
616
- }], ctorParameters: function () { return [{ type: ToastService }, { type: ThemeService }]; }, propDecorators: { themeClass: [{
617
- type: HostBinding,
618
- args: ['class']
619
- }] } });
620
-
621
- class NotificationPanelComponent {
622
- constructor(authService, toastService, themeService) {
623
- this.authService = authService;
624
- this.toastService = toastService;
625
- this.themeService = themeService;
626
- this.notificationRead = new EventEmitter();
627
- this.isOpen = false;
628
- this.notifications = [];
629
- this.currentTheme = 'light';
630
- this.activeTab = 'unread'; // Default to unread tab
631
- this.destroy$ = new Subject();
632
- }
633
- get themeClass() {
634
- return `theme-${this.currentTheme}`;
635
- }
636
- get unreadNotifications() {
637
- return this.notifications.filter(n => !n.isRead);
638
- }
639
- get readNotifications() {
640
- return this.notifications.filter(n => n.isRead);
641
- }
642
- get currentNotifications() {
643
- return this.activeTab === 'unread' ? this.unreadNotifications : this.readNotifications;
644
- }
645
- getNotificationMessage(notification) {
646
- return notification.messageHtml || notification.message || '';
647
- }
648
- ngOnInit() {
649
- this.themeService.currentTheme$
650
- .pipe(takeUntil(this.destroy$))
651
- .subscribe(theme => {
652
- this.currentTheme = theme;
653
- });
654
- this.loadNotifications();
655
- // Listen for new real-time notifications
656
- this.authService.notifications$
657
- .pipe(takeUntil(this.destroy$))
658
- .subscribe((notification) => {
659
- // Show toast for new notification
660
- this.toastService.show(notification.messageHtml || notification.message || '', notification.title, 'info', 5000);
661
- // Reload notifications list
662
- this.loadNotifications();
663
- });
664
- }
665
- ngOnDestroy() {
666
- this.destroy$.next();
667
- this.destroy$.complete();
668
- }
669
- loadNotifications() {
670
- this.authService.getNotifications(1, 50, true).subscribe({
671
- next: (response) => {
672
- this.notifications = response.items || [];
673
- },
674
- error: (err) => { }
675
- });
676
- }
677
- open() {
678
- this.isOpen = true;
679
- this.activeTab = 'unread'; // Reset to unread tab when opening
680
- }
681
- close() {
682
- this.isOpen = false;
683
- }
684
- switchTab(tab) {
685
- this.activeTab = tab;
686
- }
687
- markAsRead(notificationId, event) {
688
- if (event) {
689
- event.stopPropagation();
690
- }
691
- this.authService.markAsRead(notificationId).subscribe({
692
- next: () => {
693
- const notification = this.notifications.find(n => n.id === notificationId);
694
- if (notification) {
695
- notification.isRead = true;
696
- this.notificationRead.emit();
697
- }
698
- },
699
- error: (err) => { }
700
- });
701
- }
702
- markAllAsRead() {
703
- this.authService.markAllAsRead().subscribe({
704
- next: () => {
705
- this.notifications.forEach(n => n.isRead = true);
706
- this.notificationRead.emit();
707
- },
708
- error: (err) => { }
709
- });
710
- }
711
- deleteAllRead() {
712
- const readNotificationIds = this.notifications
713
- .filter(n => n.isRead)
714
- .map(n => n.id);
715
- // Delete all read notifications
716
- const deletePromises = readNotificationIds.map(id => this.authService.deleteNotification(id).toPromise());
717
- Promise.all(deletePromises).then(() => {
718
- // Remove all read notifications from the local array
719
- this.notifications = this.notifications.filter(n => !n.isRead);
720
- }).catch((err) => {
721
- // If bulk delete fails, reload notifications to get current state
722
- this.loadNotifications();
723
- });
724
- }
725
- deleteAllUnread() {
726
- const unreadNotificationIds = this.notifications
727
- .filter(n => !n.isRead)
728
- .map(n => n.id);
729
- // Delete all unread notifications
730
- const deletePromises = unreadNotificationIds.map(id => this.authService.deleteNotification(id).toPromise());
731
- Promise.all(deletePromises).then(() => {
732
- // Remove all unread notifications from the local array
733
- this.notifications = this.notifications.filter(n => n.isRead);
734
- }).catch((err) => {
735
- // If bulk delete fails, reload notifications to get current state
736
- this.loadNotifications();
737
- });
738
- }
739
- delete(notificationId, event) {
740
- event.stopPropagation();
741
- this.authService.deleteNotification(notificationId).subscribe({
742
- next: () => {
743
- this.notifications = this.notifications.filter(n => n.id !== notificationId);
744
- },
745
- error: (err) => { }
746
- });
747
- }
748
- formatDate(dateString) {
749
- const date = new Date(dateString);
750
- const now = new Date();
751
- const diffMs = now.getTime() - date.getTime();
752
- const diffMins = Math.floor(diffMs / 60000);
753
- const diffHours = Math.floor(diffMs / 3600000);
754
- const diffDays = Math.floor(diffMs / 86400000);
755
- if (diffMins < 1)
756
- return 'Now';
757
- if (diffMins < 60)
758
- return `${diffMins}m ago`;
759
- if (diffHours < 24)
760
- return `${diffHours}h ago`;
761
- if (diffDays < 7)
762
- return `${diffDays}d ago`;
763
- return date.toLocaleDateString();
764
- }
765
- }
766
- NotificationPanelComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
767
- NotificationPanelComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
768
- <div class="notification-panel" [class.open]="isOpen">
769
- <!-- Header -->
770
- <div class="panel-header">
771
- <h3>Notifications</h3>
772
- <button class="close-btn" (click)="close()" title="Close">✕</button>
773
- </div>
774
-
775
- <!-- Tabs -->
776
- <div class="tabs">
777
- <button
778
- class="tab-btn"
779
- [class.active]="activeTab === 'unread'"
780
- (click)="switchTab('unread')"
781
- >
782
- Unread ({{ unreadNotifications.length }})
783
- </button>
784
- <button
785
- class="tab-btn"
786
- [class.active]="activeTab === 'read'"
787
- (click)="switchTab('read')"
788
- >
789
- Read ({{ readNotifications.length }})
790
- </button>
791
- </div>
792
-
793
- <!-- Notifications List -->
794
- <div class="notifications-list">
795
- <ng-container *ngIf="currentNotifications.length > 0">
796
- <div
797
- *ngFor="let notification of currentNotifications"
798
- class="notification-item"
799
- [class.unread]="!notification.isRead"
800
- (click)="markAsRead(notification.id)"
801
- >
802
- <div class="notification-content">
803
- <div class="notification-title">{{ notification.title }}</div>
804
- <div class="notification-message" [innerHTML]="getNotificationMessage(notification)"></div>
805
- <div class="notification-meta">
806
- <span class="app-name">{{ notification.sourceAppName }}</span>
807
- <span class="time">{{ formatDate(notification.createdAt) }}</span>
808
- </div>
809
- </div>
810
- <button
811
- class="read-btn"
812
- (click)="markAsRead(notification.id, $event)"
813
- title="Mark as read"
814
- *ngIf="!notification.isRead"
815
- >
816
-
817
- </button>
818
- <button
819
- class="delete-btn"
820
- (click)="delete(notification.id, $event)"
821
- title="Delete notification"
822
- *ngIf="notification.isRead"
823
- >
824
-
825
- </button>
826
- </div>
827
- </ng-container>
828
-
829
- <ng-container *ngIf="currentNotifications.length === 0">
830
- <div class="empty-state">
831
- No {{ activeTab }} notifications
832
- </div>
833
- </ng-container>
834
- </div>
835
-
836
- <!-- Footer Actions -->
837
- <div class="panel-footer" *ngIf="currentNotifications.length > 0">
838
- <div class="footer-actions" *ngIf="activeTab === 'unread'">
839
- <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
840
- Mark all as read
841
- </button>
842
- <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
843
- Delete all
844
- </button>
845
- </div>
846
- <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
847
- Delete all
848
- </button>
849
- </div>
850
- </div>
851
- `, isInline: true, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #4caf50;--error-color: #f44336;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--bg-unread: #e3f2fd;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--success-color: #81c784;--error-color: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--bg-unread: rgba(144, 202, 249, .1);--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3)}.notification-panel{position:fixed;top:0;right:-350px;width:350px;height:100vh;background:var(--bg-primary);box-shadow:-2px 0 8px var(--shadow);display:flex;flex-direction:column;z-index:1000;transition:right .3s ease}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.panel-header h3{margin:0;font-size:18px;color:var(--text-primary)}.close-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-btn:hover{color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.tab-btn{flex:1;padding:12px 16px;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:500;transition:all .2s;border-bottom:2px solid transparent}.tab-btn:hover{background-color:var(--bg-hover);color:var(--text-primary)}.tab-btn.active{color:var(--primary-color);border-bottom-color:var(--primary-color);background-color:var(--bg-primary)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border-light);cursor:pointer;background-color:var(--bg-tertiary);transition:background-color .2s}.notification-item:hover{background-color:var(--bg-hover)}.notification-item.unread{background-color:var(--bg-unread)}.notification-content{flex:1;min-width:0}.notification-title{font-weight:600;color:var(--text-primary);font-size:14px;margin-bottom:4px}.notification-message{color:var(--text-secondary);font-size:13px;line-height:1.4;margin-bottom:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}.app-name{font-weight:500;color:var(--primary-color)}.read-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.read-btn:hover{color:var(--success-color)}.delete-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.delete-btn:hover{color:var(--error-color)}.empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}.panel-footer{padding:12px 16px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{width:100%;padding:8px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .2s}.action-btn:hover{background-color:var(--primary-hover)}.delete-all-btn{background-color:var(--error-color);color:#fff}.delete-all-btn:hover{background-color:#d32f2f}@media (max-width: 600px){.notification-panel{width:100%;right:-100%}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
852
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NotificationPanelComponent, decorators: [{
853
- type: Component,
854
- args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
855
- <div class="notification-panel" [class.open]="isOpen">
856
- <!-- Header -->
857
- <div class="panel-header">
858
- <h3>Notifications</h3>
859
- <button class="close-btn" (click)="close()" title="Close">✕</button>
860
- </div>
861
-
862
- <!-- Tabs -->
863
- <div class="tabs">
864
- <button
865
- class="tab-btn"
866
- [class.active]="activeTab === 'unread'"
867
- (click)="switchTab('unread')"
868
- >
869
- Unread ({{ unreadNotifications.length }})
870
- </button>
871
- <button
872
- class="tab-btn"
873
- [class.active]="activeTab === 'read'"
874
- (click)="switchTab('read')"
875
- >
876
- Read ({{ readNotifications.length }})
877
- </button>
878
- </div>
879
-
880
- <!-- Notifications List -->
881
- <div class="notifications-list">
882
- <ng-container *ngIf="currentNotifications.length > 0">
883
- <div
884
- *ngFor="let notification of currentNotifications"
885
- class="notification-item"
886
- [class.unread]="!notification.isRead"
887
- (click)="markAsRead(notification.id)"
888
- >
889
- <div class="notification-content">
890
- <div class="notification-title">{{ notification.title }}</div>
891
- <div class="notification-message" [innerHTML]="getNotificationMessage(notification)"></div>
892
- <div class="notification-meta">
893
- <span class="app-name">{{ notification.sourceAppName }}</span>
894
- <span class="time">{{ formatDate(notification.createdAt) }}</span>
895
- </div>
896
- </div>
897
- <button
898
- class="read-btn"
899
- (click)="markAsRead(notification.id, $event)"
900
- title="Mark as read"
901
- *ngIf="!notification.isRead"
902
- >
903
-
904
- </button>
905
- <button
906
- class="delete-btn"
907
- (click)="delete(notification.id, $event)"
908
- title="Delete notification"
909
- *ngIf="notification.isRead"
910
- >
911
-
912
- </button>
913
- </div>
914
- </ng-container>
915
-
916
- <ng-container *ngIf="currentNotifications.length === 0">
917
- <div class="empty-state">
918
- No {{ activeTab }} notifications
919
- </div>
920
- </ng-container>
921
- </div>
922
-
923
- <!-- Footer Actions -->
924
- <div class="panel-footer" *ngIf="currentNotifications.length > 0">
925
- <div class="footer-actions" *ngIf="activeTab === 'unread'">
926
- <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
927
- Mark all as read
928
- </button>
929
- <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
930
- Delete all
931
- </button>
932
- </div>
933
- <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
934
- Delete all
935
- </button>
936
- </div>
937
- </div>
938
- `, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #4caf50;--error-color: #f44336;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--bg-unread: #e3f2fd;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--success-color: #81c784;--error-color: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--bg-unread: rgba(144, 202, 249, .1);--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3)}.notification-panel{position:fixed;top:0;right:-350px;width:350px;height:100vh;background:var(--bg-primary);box-shadow:-2px 0 8px var(--shadow);display:flex;flex-direction:column;z-index:1000;transition:right .3s ease}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.panel-header h3{margin:0;font-size:18px;color:var(--text-primary)}.close-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-btn:hover{color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.tab-btn{flex:1;padding:12px 16px;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:500;transition:all .2s;border-bottom:2px solid transparent}.tab-btn:hover{background-color:var(--bg-hover);color:var(--text-primary)}.tab-btn.active{color:var(--primary-color);border-bottom-color:var(--primary-color);background-color:var(--bg-primary)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border-light);cursor:pointer;background-color:var(--bg-tertiary);transition:background-color .2s}.notification-item:hover{background-color:var(--bg-hover)}.notification-item.unread{background-color:var(--bg-unread)}.notification-content{flex:1;min-width:0}.notification-title{font-weight:600;color:var(--text-primary);font-size:14px;margin-bottom:4px}.notification-message{color:var(--text-secondary);font-size:13px;line-height:1.4;margin-bottom:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}.app-name{font-weight:500;color:var(--primary-color)}.read-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.read-btn:hover{color:var(--success-color)}.delete-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.delete-btn:hover{color:var(--error-color)}.empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}.panel-footer{padding:12px 16px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{width:100%;padding:8px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .2s}.action-btn:hover{background-color:var(--primary-hover)}.delete-all-btn{background-color:var(--error-color);color:#fff}.delete-all-btn:hover{background-color:#d32f2f}@media (max-width: 600px){.notification-panel{width:100%;right:-100%}}\n"] }]
939
- }], ctorParameters: function () { return [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }]; }, propDecorators: { notificationRead: [{
940
- type: Output
941
- }], themeClass: [{
942
- type: HostBinding,
943
- args: ['class']
944
- }] } });
945
-
946
- class MaUserComponent {
947
- onNotificationRead() {
948
- this.userProfile.loadUnreadCount();
949
- }
950
- }
951
- MaUserComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
952
- MaUserComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: MaUserComponent, isStandalone: true, selector: "ma-user", viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }], ngImport: i0, template: `
953
- <ma-toast-container></ma-toast-container>
954
- <div class="user-header">
955
- <ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
956
- </div>
957
- <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
958
- `, isInline: true, styles: [".user-header{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "component", type: ToastContainerComponent, selector: "ma-toast-container" }, { kind: "component", type: UserProfileComponent, selector: "ma-user-profile", outputs: ["notificationClick"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }] });
959
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: MaUserComponent, decorators: [{
960
- type: Component,
961
- args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
962
- <ma-toast-container></ma-toast-container>
963
- <div class="user-header">
964
- <ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
965
- </div>
966
- <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
967
- `, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
968
- }], propDecorators: { userProfile: [{
969
- type: ViewChild,
970
- args: [UserProfileComponent]
971
- }] } });
972
-
973
- class NotificationBadgeComponent {
974
- constructor(authService, themeService) {
975
- this.authService = authService;
976
- this.themeService = themeService;
977
- this.notificationClick = new EventEmitter();
978
- this.unreadCount = 0;
979
- this.currentTheme = 'light';
980
- this.destroy$ = new Subject();
981
- }
982
- get themeClass() {
983
- return `theme-${this.currentTheme}`;
984
- }
985
- ngOnInit() {
986
- this.themeService.currentTheme$
987
- .pipe(takeUntil(this.destroy$))
988
- .subscribe(theme => {
989
- this.currentTheme = theme;
990
- });
991
- this.loadUnreadCount();
992
- // Listen for new notifications
993
- this.authService.notifications$
994
- .pipe(takeUntil(this.destroy$))
995
- .subscribe(() => {
996
- this.loadUnreadCount();
997
- });
998
- }
999
- ngOnDestroy() {
1000
- this.destroy$.next();
1001
- this.destroy$.complete();
1002
- }
1003
- loadUnreadCount() {
1004
- this.authService.getUnreadCount().subscribe({
1005
- next: (response) => {
1006
- this.unreadCount = response.unreadCount || 0;
1007
- },
1008
- error: (err) => console.error('Error loading unread count:', err)
1009
- });
1010
- }
1011
- onNotificationClick() {
1012
- this.notificationClick.emit();
1013
- }
1014
- }
1015
- NotificationBadgeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NotificationBadgeComponent, deps: [{ token: MesAuthService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
1016
- NotificationBadgeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.3.0", type: NotificationBadgeComponent, isStandalone: true, selector: "ma-notification-badge", outputs: { notificationClick: "notificationClick" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
1017
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
1018
- <span class="icon">🔔</span>
1019
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
1020
- </button>
1021
- `, isInline: true, styles: [":host{--error-color: #f44336}:host(.theme-dark){--error-color: #ef5350}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
1022
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: NotificationBadgeComponent, decorators: [{
1023
- type: Component,
1024
- args: [{ selector: 'ma-notification-badge', standalone: true, imports: [NgIf], template: `
1025
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
1026
- <span class="icon">🔔</span>
1027
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
1028
- </button>
1029
- `, styles: [":host{--error-color: #f44336}:host(.theme-dark){--error-color: #ef5350}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"] }]
1030
- }], ctorParameters: function () { return [{ type: MesAuthService }, { type: ThemeService }]; }, propDecorators: { notificationClick: [{
1031
- type: Output
1032
- }], themeClass: [{
1033
- type: HostBinding,
1034
- args: ['class']
1035
- }] } });
1036
-
1037
- /**
1038
- * Generated bundle index. Do not edit.
1039
- */
1040
-
1041
- export { MaUserComponent, MesAuthInterceptor, MesAuthModule, MesAuthService, NotificationBadgeComponent, NotificationPanelComponent, NotificationType, ThemeService, ToastContainerComponent, UserProfileComponent };
1042
- //# sourceMappingURL=mesauth-angular.mjs.map