mesauth-angular 0.2.14 → 0.2.20

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