mesauth-angular 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/README.md +6 -0
- package/dist/fesm2022/mesauth-angular.mjs +32 -69
- package/dist/fesm2022/mesauth-angular.mjs.map +1 -1
- package/dist/index.d.ts +6 -3
- package/mesauth-angular-1.0.0.tgz +0 -0
- package/mesauth-angular-1.0.1.tgz +0 -0
- package/mesauth-angular-1.1.0.tgz +0 -0
- package/ng-package.json +8 -0
- package/package.json +1 -6
- package/src/index.ts +10 -0
- package/src/ma-user.component.ts +39 -0
- package/src/mes-auth.interceptor.ts +59 -0
- package/src/mes-auth.module.ts +9 -0
- package/src/mes-auth.service.ts +406 -0
- package/src/notification-badge.component.ts +125 -0
- package/src/notification-panel.component.ts +672 -0
- package/src/theme.service.ts +70 -0
- package/src/toast-container.component.ts +221 -0
- package/src/toast.service.ts +47 -0
- package/src/user-profile.component.ts +449 -0
- package/tsconfig.json +22 -0
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@ Angular helper library to connect to a backend API and SignalR hub to surface th
|
|
|
4
4
|
|
|
5
5
|
## Changelog
|
|
6
6
|
|
|
7
|
+
### v1.2.0 (2026-02-05) - **Notification & Auth Interceptor Fix**
|
|
8
|
+
- **Removed route-change user polling**: `MesAuthService` no longer re-fetches the user on every `NavigationEnd` event. User is fetched once on app init; SignalR handles real-time updates. This eliminates redundant API calls and notification toast spam on every route change.
|
|
9
|
+
- **Removed `fetchInitialNotifications()`**: Historical notifications were being emitted through `notifications$` Subject on every user refresh, causing toast popups for old notifications. The `notifications$` observable now only carries truly new real-time events from SignalR.
|
|
10
|
+
- **`refreshUser()` returns `Observable`**: Callers can now subscribe and wait for user data to load before proceeding (e.g., navigate after login). Previously returned `void`.
|
|
11
|
+
- **Fixed 401 redirect for expired sessions**: Removed the `!isAuthenticated` guard from the interceptor's 401 condition. When a session expires, the `BehaviorSubject` still holds stale user data, so this check was blocking the redirect to login. The `!isMeAuthPage`, `!isLoginPage`, and `!isAuthPage` guards are sufficient to prevent redirect loops.
|
|
12
|
+
|
|
7
13
|
### v1.1.0 (2026-01-21) - **Major Update**
|
|
8
14
|
- 🚀 **New `provideMesAuth()` Function**: Simplified setup with a single function call
|
|
9
15
|
- ✨ **Functional Interceptor**: New `mesAuthInterceptor` for better compatibility with standalone apps
|
package/dist/README.md
CHANGED
|
@@ -4,6 +4,12 @@ Angular helper library to connect to a backend API and SignalR hub to surface th
|
|
|
4
4
|
|
|
5
5
|
## Changelog
|
|
6
6
|
|
|
7
|
+
### v1.2.0 (2026-02-05) - **Notification & Auth Interceptor Fix**
|
|
8
|
+
- **Removed route-change user polling**: `MesAuthService` no longer re-fetches the user on every `NavigationEnd` event. User is fetched once on app init; SignalR handles real-time updates. This eliminates redundant API calls and notification toast spam on every route change.
|
|
9
|
+
- **Removed `fetchInitialNotifications()`**: Historical notifications were being emitted through `notifications$` Subject on every user refresh, causing toast popups for old notifications. The `notifications$` observable now only carries truly new real-time events from SignalR.
|
|
10
|
+
- **`refreshUser()` returns `Observable`**: Callers can now subscribe and wait for user data to load before proceeding (e.g., navigate after login). Previously returned `void`.
|
|
11
|
+
- **Fixed 401 redirect for expired sessions**: Removed the `!isAuthenticated` guard from the interceptor's 401 condition. When a session expires, the `BehaviorSubject` still holds stale user data, so this check was blocking the redirect to login. The `!isMeAuthPage`, `!isLoginPage`, and `!isAuthPage` guards are sufficient to prevent redirect loops.
|
|
12
|
+
|
|
7
13
|
### v1.1.0 (2026-01-21) - **Major Update**
|
|
8
14
|
- 🚀 **New `provideMesAuth()` Function**: Simplified setup with a single function call
|
|
9
15
|
- ✨ **Functional Interceptor**: New `mesAuthInterceptor` for better compatibility with standalone apps
|
|
@@ -2,10 +2,10 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
4
|
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
|
5
|
-
import { BehaviorSubject, Subject, throwError } from 'rxjs';
|
|
6
|
-
import {
|
|
5
|
+
import { BehaviorSubject, Subject, EMPTY, of, throwError } from 'rxjs';
|
|
6
|
+
import { tap, catchError, takeUntil } from 'rxjs/operators';
|
|
7
7
|
import * as i2 from '@angular/router';
|
|
8
|
-
import { Router
|
|
8
|
+
import { Router } from '@angular/router';
|
|
9
9
|
import * as i3 from '@angular/common';
|
|
10
10
|
import { NgIf, CommonModule, NgFor } from '@angular/common';
|
|
11
11
|
|
|
@@ -61,75 +61,35 @@ class MesAuthService {
|
|
|
61
61
|
constructor() {
|
|
62
62
|
// Empty constructor - all dependencies passed to init()
|
|
63
63
|
}
|
|
64
|
-
isProtectedRoute(url) {
|
|
65
|
-
// Consider routes protected if they don't include auth-related paths
|
|
66
|
-
return !url.includes('/login') && !url.includes('/auth') && !url.includes('/signin') && !url.includes('/logout');
|
|
67
|
-
}
|
|
68
64
|
init(config, httpClient, router) {
|
|
69
65
|
this.config = config;
|
|
70
66
|
this.http = httpClient;
|
|
71
67
|
this.router = router;
|
|
72
68
|
this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.pipe(filter(event => event instanceof NavigationEnd), debounceTime(1000) // Longer debounce to avoid interfering with login flow
|
|
78
|
-
)
|
|
79
|
-
.subscribe((event) => {
|
|
80
|
-
// Only refresh if user is logged in and navigating to protected routes
|
|
81
|
-
// Avoid refreshing during login/logout flows
|
|
82
|
-
if (this._currentUser.value && this.isProtectedRoute(event.url)) {
|
|
83
|
-
// Small delay to ensure any login/logout operations complete
|
|
84
|
-
setTimeout(() => {
|
|
85
|
-
if (this._currentUser.value) {
|
|
86
|
-
this.refreshUser();
|
|
87
|
-
}
|
|
88
|
-
}, 100);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
this.fetchCurrentUser();
|
|
69
|
+
// Fetch user once on init. Route changes do NOT re-fetch the user.
|
|
70
|
+
// Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.
|
|
71
|
+
// SignalR handles real-time notification delivery without polling.
|
|
72
|
+
this.fetchCurrentUser().subscribe();
|
|
93
73
|
}
|
|
94
74
|
getConfig() {
|
|
95
75
|
return this.config;
|
|
96
76
|
}
|
|
97
77
|
fetchCurrentUser() {
|
|
98
78
|
if (!this.apiBase)
|
|
99
|
-
return;
|
|
79
|
+
return EMPTY;
|
|
100
80
|
const url = `${this.apiBase}/auth/me`;
|
|
101
|
-
this.http.get(url, { withCredentials: this.config?.withCredentials ?? true }).
|
|
102
|
-
next
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.startConnection(this.config);
|
|
106
|
-
// Only fetch notifications after confirming user is logged in
|
|
107
|
-
this.fetchInitialNotifications();
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
error: (err) => {
|
|
111
|
-
// Silently handle auth errors (401/403) - user is not logged in
|
|
112
|
-
if (err.status === 401 || err.status === 403) {
|
|
113
|
-
this._currentUser.next(null);
|
|
114
|
-
}
|
|
81
|
+
return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true }).pipe(tap((u) => {
|
|
82
|
+
this._currentUser.next(u);
|
|
83
|
+
if (u && this.config) {
|
|
84
|
+
this.startConnection(this.config);
|
|
115
85
|
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!this.apiBase || !this._currentUser.value)
|
|
121
|
-
return;
|
|
122
|
-
this.http.get(`${this.apiBase}/notif/me`, { withCredentials: this.config?.withCredentials ?? true }).subscribe({
|
|
123
|
-
next: (notifications) => {
|
|
124
|
-
if (Array.isArray(notifications?.items)) {
|
|
125
|
-
notifications.items.forEach((n) => this._notifications.next(n));
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
error: (err) => {
|
|
129
|
-
// Silently handle auth errors (401/403) - user is not logged in
|
|
130
|
-
// No need to emit anything
|
|
86
|
+
}), catchError((err) => {
|
|
87
|
+
// Silently handle auth errors (401/403) - user is not logged in
|
|
88
|
+
if (err.status === 401 || err.status === 403) {
|
|
89
|
+
this._currentUser.next(null);
|
|
131
90
|
}
|
|
132
|
-
|
|
91
|
+
return of(null);
|
|
92
|
+
}));
|
|
133
93
|
}
|
|
134
94
|
getUnreadCount() {
|
|
135
95
|
return this.http.get(`${this.apiBase}/notif/me/unread-count`, { withCredentials: this.config?.withCredentials ?? true });
|
|
@@ -290,8 +250,13 @@ class MesAuthService {
|
|
|
290
250
|
get isAuthenticated() {
|
|
291
251
|
return this._currentUser.value !== null;
|
|
292
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Refreshes the current user from the server.
|
|
255
|
+
* Returns an Observable that completes when the user data is loaded.
|
|
256
|
+
* Callers can subscribe to wait for completion before proceeding (e.g., navigating after login).
|
|
257
|
+
*/
|
|
293
258
|
refreshUser() {
|
|
294
|
-
this.fetchCurrentUser();
|
|
259
|
+
return this.fetchCurrentUser();
|
|
295
260
|
}
|
|
296
261
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
297
262
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthService });
|
|
@@ -316,20 +281,19 @@ const mesAuthInterceptor = (req, next) => {
|
|
|
316
281
|
if ((status === 401 || status === 403) && !isRedirecting) {
|
|
317
282
|
const config = authService.getConfig();
|
|
318
283
|
const baseUrl = config?.userBaseUrl || '';
|
|
319
|
-
// Use router URL for internal navigation (cleaner URLs)
|
|
320
|
-
// Falls back to window.location for full URL if needed
|
|
321
284
|
const currentUrl = router.url + (window.location.hash || '');
|
|
322
285
|
const returnUrl = encodeURIComponent(currentUrl);
|
|
323
286
|
// Avoid loops if already on auth/unauth pages
|
|
324
287
|
const isLoginPage = currentUrl.includes('/login');
|
|
325
288
|
const is403Page = currentUrl.includes('/403');
|
|
326
289
|
const isAuthPage = currentUrl.includes('/auth');
|
|
290
|
+
// Skip redirect for the initial /auth/me check (app startup when not logged in)
|
|
327
291
|
const isMeAuthPage = req.url.includes('/auth/me');
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
292
|
+
if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage) {
|
|
293
|
+
// Session expired or not authenticated - redirect to login
|
|
294
|
+
// No isAuthenticated check: when session expires, BehaviorSubject still holds
|
|
295
|
+
// stale user data, so checking isAuthenticated would block the redirect.
|
|
331
296
|
isRedirecting = true;
|
|
332
|
-
// Reset flag after a delay to allow future redirects after user returns
|
|
333
297
|
setTimeout(() => { isRedirecting = false; }, 5000);
|
|
334
298
|
window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
|
|
335
299
|
}
|
|
@@ -467,11 +431,10 @@ class UserProfileComponent {
|
|
|
467
431
|
.subscribe(theme => {
|
|
468
432
|
this.currentTheme = theme;
|
|
469
433
|
});
|
|
470
|
-
// Listen for new notifications
|
|
434
|
+
// Listen for new real-time notifications (SignalR only)
|
|
471
435
|
this.authService.notifications$
|
|
472
436
|
.pipe(takeUntil(this.destroy$))
|
|
473
437
|
.subscribe(() => {
|
|
474
|
-
console.log('Notification received, updating unread count');
|
|
475
438
|
if (this.hasUser) {
|
|
476
439
|
this.loadUnreadCount();
|
|
477
440
|
}
|
|
@@ -1055,7 +1018,7 @@ class NotificationPanelComponent {
|
|
|
1055
1018
|
</div>
|
|
1056
1019
|
</div>
|
|
1057
1020
|
</div>
|
|
1058
|
-
`, isInline: true, styles: [":host{
|
|
1021
|
+
`, isInline: true, styles: [":host{display:block;position:relative;--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){display:block;position:relative;--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:12px;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}.modal-overlay{position:fixed;inset:0;width:100vw;height:100vh;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.modal-container{background:var(--bg-primary);border-radius:8px;width:90%;max-width:600px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px var(--shadow)}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:8px 8px 0 0}.modal-header h3{margin:0;font-size:18px;color:var(--text-primary)}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:12px;color:var(--text-muted);background-color:var(--bg-tertiary);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.6}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:0 0 8px 8px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
1059
1022
|
}
|
|
1060
1023
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1061
1024
|
type: Component,
|
|
@@ -1161,7 +1124,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1161
1124
|
</div>
|
|
1162
1125
|
</div>
|
|
1163
1126
|
</div>
|
|
1164
|
-
`, styles: [":host{
|
|
1127
|
+
`, styles: [":host{display:block;position:relative;--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){display:block;position:relative;--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:12px;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}.modal-overlay{position:fixed;inset:0;width:100vw;height:100vh;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.modal-container{background:var(--bg-primary);border-radius:8px;width:90%;max-width:600px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px var(--shadow)}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:8px 8px 0 0}.modal-header h3{margin:0;font-size:18px;color:var(--text-primary)}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:12px;color:var(--text-muted);background-color:var(--bg-tertiary);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.6}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:0 0 8px 8px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1165
1128
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
|
|
1166
1129
|
type: Output
|
|
1167
1130
|
}], themeClass: [{
|