mesauth-angular 1.2.1 → 1.2.2

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 CHANGED
@@ -4,6 +4,11 @@ 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.2 (2026-02-06) - **Z-Index Fix for Notification UI**
8
+ - **Fixed notification panel z-index**: Increased from `1000` to `1030` to appear above CoreUI sticky table headers (`z-index: 1020`)
9
+ - **Fixed modal overlay z-index**: Increased from `9999` to `1060` to maintain proper layering hierarchy
10
+ - **Better integration with CoreUI/Bootstrap**: Follows standard z-index scale (sticky: 1020, modals: 1050+, overlays: 1060+)
11
+
7
12
  ### v1.2.0 (2026-02-05) - **Notification & Auth Interceptor Fix**
8
13
  - **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
14
  - **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.
package/dist/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # mesauth-angular
2
+
3
+ Angular helper library to connect to a backend API and SignalR hub to surface the current logged-in user and incoming notifications with dark/light theme support.
4
+
5
+ ## Changelog
6
+
7
+ ### v1.2.2 (2026-02-06) - **Z-Index Fix for Notification UI**
8
+ - **Fixed notification panel z-index**: Increased from `1000` to `1030` to appear above CoreUI sticky table headers (`z-index: 1020`)
9
+ - **Fixed modal overlay z-index**: Increased from `9999` to `1060` to maintain proper layering hierarchy
10
+ - **Better integration with CoreUI/Bootstrap**: Follows standard z-index scale (sticky: 1020, modals: 1050+, overlays: 1060+)
11
+
12
+ ### v1.2.0 (2026-02-05) - **Notification & Auth Interceptor Fix**
13
+ - **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.
14
+ - **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.
15
+ - **`refreshUser()` returns `Observable`**: Callers can now subscribe and wait for user data to load before proceeding (e.g., navigate after login). Previously returned `void`.
16
+ - **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.
17
+
18
+ ### v1.1.0 (2026-01-21) - **Major Update**
19
+ - 🚀 **New `provideMesAuth()` Function**: Simplified setup with a single function call
20
+ - ✨ **Functional Interceptor**: New `mesAuthInterceptor` for better compatibility with standalone apps
21
+ - 📦 **Automatic Initialization**: `provideMesAuth()` handles service initialization via `APP_INITIALIZER`
22
+ - 🔧 **Simplified API**: Just pass `apiBaseUrl` and `userBaseUrl` - no manual DI required
23
+
24
+ ### v1.0.1 (2026-01-21)
25
+ - 🔧 Internal refactoring for better module compatibility
26
+
27
+ ### v0.2.28 (2026-01-19)
28
+ - ✨ **Enhanced Avatar Support**: Direct `avatarPath` usage from user data for instant display without backend calls
29
+ - 🔄 **Improved Avatar Refresh**: Timestamp-based cache busting prevents request cancellation issues
30
+ - 🎯 **Better Change Detection**: Signal-based user updates with `ChangeDetectorRef` for reliable UI updates
31
+
32
+ ### v0.2.27 (2026-01-19)
33
+ - 🐛 Fixed avatar refresh issues in header components
34
+ - 📦 Improved build process and dependencies
35
+
36
+ ## Features
37
+
38
+ - 🔐 **Authentication**: User login/logout with API integration
39
+ - 🔔 **Real-time Notifications**: SignalR integration for live notifications
40
+ - 🎨 **Dark/Light Theme**: Automatic theme detection and support
41
+ - 🖼️ **Avatar Support**: Direct API-based avatar loading
42
+ - 🍞 **Toast Notifications**: In-app notification toasts
43
+ - 🛡️ **HTTP Interceptor**: Automatic 401/403 error handling with redirects
44
+
45
+ ## Quick Start (v1.1.0+)
46
+
47
+ ### 1. Install
48
+
49
+ ```bash
50
+ npm install mesauth-angular
51
+ ```
52
+
53
+ ### 2. Configure in app.config.ts (Recommended for Angular 14+)
54
+
55
+ ```ts
56
+ import { ApplicationConfig } from '@angular/core';
57
+ import { provideHttpClient, withInterceptors } from '@angular/common/http';
58
+ import { provideMesAuth, mesAuthInterceptor } from 'mesauth-angular';
59
+
60
+ export const appConfig: ApplicationConfig = {
61
+ providers: [
62
+ provideHttpClient(
63
+ withInterceptors([mesAuthInterceptor]) // Handles 401/403 redirects
64
+ ),
65
+ provideMesAuth({
66
+ apiBaseUrl: 'https://mes.kefico.vn/auth',
67
+ userBaseUrl: 'https://mes.kefico.vn/x' // For login/403 redirects
68
+ })
69
+ ]
70
+ };
71
+ ```
72
+
73
+ That's it! The library handles:
74
+ - Service initialization via `APP_INITIALIZER`
75
+ - `HttpClient` and `Router` injection automatically
76
+ - 401 → redirects to `{userBaseUrl}/login?returnUrl=...`
77
+ - 403 → redirects to `{userBaseUrl}/403?returnUrl=...`
78
+
79
+ ### 3. Use in Components
80
+
81
+ ```ts
82
+ import { MesAuthService } from 'mesauth-angular';
83
+
84
+ @Component({...})
85
+ export class MyComponent {
86
+ private auth = inject(MesAuthService);
87
+
88
+ // Observable streams
89
+ currentUser$ = this.auth.currentUser$;
90
+ notifications$ = this.auth.notifications$;
91
+
92
+ logout() {
93
+ this.auth.logout().subscribe();
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Configuration Options
99
+
100
+ ```ts
101
+ interface MesAuthConfig {
102
+ apiBaseUrl: string; // Required: MesAuth API base URL
103
+ userBaseUrl?: string; // Optional: Base URL for login/403 redirects
104
+ withCredentials?: boolean; // Optional: Send cookies (default: true)
105
+ }
106
+ ```
107
+
108
+ ## Theme Support
109
+
110
+ The library automatically detects and adapts to your application's theme:
111
+
112
+ ### Automatic Theme Detection
113
+ The library checks for theme indicators on the `<html>` element:
114
+ - `class="dark"`
115
+ - `data-theme="dark"`
116
+ - `theme="dark"`
117
+ - `data-coreui-theme="dark"`
118
+
119
+ ### Dynamic Theme Changes
120
+ Theme changes are detected in real-time using `MutationObserver`, so components automatically update when your app switches themes.
121
+
122
+ ### Manual Theme Control
123
+ ```ts
124
+ import { ThemeService } from 'mesauth-angular';
125
+
126
+ // Check current theme
127
+ const currentTheme = themeService.currentTheme; // 'light' | 'dark'
128
+
129
+ // Manually set theme
130
+ themeService.setTheme('dark');
131
+
132
+ // Listen for theme changes
133
+ themeService.currentTheme$.subscribe(theme => {
134
+ console.log('Theme changed to:', theme);
135
+ });
136
+ ```
137
+
138
+ ## Avatar Loading
139
+
140
+ Avatars are loaded efficiently using multiple strategies:
141
+
142
+ ### Primary Method: Direct Path Usage
143
+ If the user object contains an `avatarPath`, it's used directly:
144
+ - **Full URLs**: Used as-is (e.g., `https://example.com/avatar.jpg`)
145
+ - **Relative Paths**: Combined with API base URL (e.g., `/uploads/avatar.jpg` → `{apiBaseUrl}/uploads/avatar.jpg`)
146
+
147
+ ### Fallback Method: API Endpoint
148
+ If no `avatarPath` is available, avatars are loaded via API:
149
+ - **API Endpoint**: `GET {apiBaseUrl}/auth/{userId}/avatar`
150
+ - **Authentication**: Uses the same credentials as other API calls
151
+
152
+ ### Cache Busting
153
+ Avatar URLs include timestamps to prevent browser caching issues during updates:
154
+ - Automatic refresh when user data changes
155
+ - Manual refresh triggers for upload/delete operations
156
+
157
+ ### Fallback Service
158
+ - **UI Avatars**: Generates initials-based avatars if no user data available
159
+ - **Authentication**: Not required for fallback avatars
160
+
161
+ ## Components
162
+
163
+ **Note:** All components are standalone and can be imported directly.
164
+
165
+ ### ma-user-profile
166
+
167
+ A reusable Angular component for displaying the current user's profile information, with options for navigation and logout.
168
+
169
+ - **Description**: Renders user details (e.g., name, avatar) fetched via the MesAuthService. Supports custom event handlers for navigation and logout actions.
170
+ - **Inputs**: None (data is sourced from the MesAuthService).
171
+ - **Outputs**:
172
+ - `onNavigate`: Emits an event when the user triggers navigation (e.g., to a profile page). Pass a handler to define behavior.
173
+ - `onLogout`: Emits an event when the user logs out. Pass a handler to perform logout logic (e.g., clear tokens, redirect).
174
+ - **Usage Example**:
175
+
176
+ ```html
177
+ <ma-user-profile
178
+ (onNavigate)="handleNavigation($event)"
179
+ (onLogout)="handleLogout()">
180
+ </ma-user-profile>
181
+ ```
182
+
183
+ In your component's TypeScript file:
184
+
185
+ ```ts
186
+ handleNavigation(event: any) {
187
+ // Navigate to user profile page
188
+ this.router.navigate(['/profile']);
189
+ }
190
+
191
+ handleLogout() {
192
+ // Perform logout, e.g., clear session and redirect
193
+ this.mesAuth.logout(); // Assuming a logout method exists
194
+ this.router.navigate(['/login']);
195
+ }
196
+ ```
197
+
198
+ ### ma-notification-panel
199
+
200
+ A standalone component for displaying a slide-out notification panel with real-time updates.
201
+
202
+ - **Description**: Shows a list of notifications, allows marking as read/delete, and integrates with toast notifications for new alerts.
203
+ - **Inputs**: None.
204
+ - **Outputs**: None (uses internal methods for actions).
205
+ - **Usage Example**:
206
+
207
+ ```html
208
+ <ma-notification-panel #notificationPanel></ma-notification-panel>
209
+ ```
210
+
211
+ In your component:
212
+
213
+ ```ts
214
+ // To open the panel
215
+ notificationPanel.open();
216
+ ```
217
+
218
+ ## Migration Guide
219
+
220
+ ### Upgrading from v0.x to v1.1.0+
221
+
222
+ The setup has been greatly simplified. Here's how to migrate:
223
+
224
+ **Before (v0.x):**
225
+ ```ts
226
+ // app.config.ts - OLD WAY
227
+ import { MesAuthModule, MesAuthService } from 'mesauth-angular';
228
+ import { HTTP_INTERCEPTORS } from '@angular/common/http';
229
+
230
+ export const appConfig: ApplicationConfig = {
231
+ providers: [
232
+ importProvidersFrom(MesAuthModule),
233
+ { provide: HTTP_INTERCEPTORS, useClass: MesAuthInterceptor, multi: true }
234
+ ]
235
+ };
236
+
237
+ // app.component.ts - OLD WAY
238
+ export class AppComponent {
239
+ constructor() {
240
+ this.mesAuthService.init({
241
+ apiBaseUrl: '...',
242
+ userBaseUrl: '...'
243
+ }, inject(HttpClient), inject(Router));
244
+ }
245
+ }
246
+ ```
247
+
248
+ **After (v1.1.0+):**
249
+ ```ts
250
+ // app.config.ts - NEW WAY (everything in one place!)
251
+ import { provideMesAuth, mesAuthInterceptor } from 'mesauth-angular';
252
+
253
+ export const appConfig: ApplicationConfig = {
254
+ providers: [
255
+ provideHttpClient(withInterceptors([mesAuthInterceptor])),
256
+ provideMesAuth({
257
+ apiBaseUrl: 'https://mes.kefico.vn/auth',
258
+ userBaseUrl: 'https://mes.kefico.vn/x'
259
+ })
260
+ ]
261
+ };
262
+
263
+ // app.component.ts - No init() needed!
264
+ export class AppComponent {
265
+ // Just inject and use - no manual initialization required
266
+ }
267
+ ```
268
+
269
+ **Key Changes:**
270
+ - `provideMesAuth()` replaces `MesAuthModule` + manual `init()` call
271
+ - `mesAuthInterceptor` (functional) replaces `MesAuthInterceptor` (class-based)
272
+ - No need to inject `HttpClient` or `Router` manually
273
+ - Configuration moved from `AppComponent` to `app.config.ts`
274
+
275
+ ## Troubleshooting
276
+
277
+ ### JIT Compiler Error in Production or AOT Mode
278
+ If you encounter an error like "The injectable 'MesAuthService' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available," this typically occurs because:
279
+ - The package is being imported directly from source code (e.g., during development) without building it first.
280
+ - The client app is running in AOT (Ahead-of-Time) compilation mode, which requires pre-compiled libraries.
281
+
282
+ **Solutions:**
283
+ 1. **Build the package for production/AOT compatibility:**
284
+ - Ensure you have built the package using `npm run build` (which uses ng-packagr or similar to generate AOT-ready code).
285
+ - Install the built package via npm (e.g., from a local tarball or registry) instead of linking to the source folder.
286
+
287
+ 2. **For development (if you must link to source):**
288
+ - Switch your Angular app to JIT mode by bootstrapping with `@angular/platform-browser-dynamic` instead of `@angular/platform-browser`.
289
+
290
+ 3. **Verify imports:**
291
+ - Ensure you're importing from the built package (e.g., `import { MesAuthService } from 'mesauth-angular';`) and not from the `src` folder.
292
+
293
+ ### Components Appear Empty
294
+ If components like `ma-user` or `ma-user-profile` render as empty:
295
+ - Ensure `provideMesAuth()` is called in your `app.config.ts`.
296
+ - Check browser console for logs from components.
297
+ - If `currentUser` is null, the component shows a login button—verify the API returns user data.
298
+
299
+ ## Notes
300
+ - The service expects an endpoint `GET {apiBaseUrl}/auth/me` that returns the current user.
301
+ - Avatar endpoint: `GET {apiBaseUrl}/auth/{userId}/avatar`
302
+ - SignalR events used: `ReceiveNotification` (adjust to your backend).
@@ -1018,7 +1018,7 @@ class NotificationPanelComponent {
1018
1018
  </div>
1019
1019
  </div>
1020
1020
  </div>
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"] }] });
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:1030;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:1060}.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"] }] });
1022
1022
  }
1023
1023
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, decorators: [{
1024
1024
  type: Component,
@@ -1124,7 +1124,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1124
1124
  </div>
1125
1125
  </div>
1126
1126
  </div>
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"] }]
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:1030;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:1060}.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"] }]
1128
1128
  }], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
1129
1129
  type: Output
1130
1130
  }], themeClass: [{