mesauth-angular 1.3.1 → 1.3.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/fesm2022/mesauth-angular.mjs +264 -214
- package/fesm2022/mesauth-angular.mjs.map +1 -1
- package/index.d.ts +11 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, 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
5
|
import { BehaviorSubject, Subject, EMPTY, of, throwError } from 'rxjs';
|
|
@@ -38,7 +38,8 @@ function provideMesAuth(config) {
|
|
|
38
38
|
const mesAuthService = inject(MesAuthService);
|
|
39
39
|
const httpClient = inject(HttpClient);
|
|
40
40
|
const router = inject(Router);
|
|
41
|
-
|
|
41
|
+
const ngZone = inject(NgZone);
|
|
42
|
+
mesAuthService.init(config, httpClient, router, ngZone);
|
|
42
43
|
})
|
|
43
44
|
]);
|
|
44
45
|
}
|
|
@@ -59,13 +60,15 @@ class MesAuthService {
|
|
|
59
60
|
config = null;
|
|
60
61
|
http;
|
|
61
62
|
router;
|
|
63
|
+
ngZone = null;
|
|
62
64
|
constructor() {
|
|
63
65
|
// Empty constructor - all dependencies passed to init()
|
|
64
66
|
}
|
|
65
|
-
init(config, httpClient, router) {
|
|
67
|
+
init(config, httpClient, router, ngZone) {
|
|
66
68
|
this.config = config;
|
|
67
69
|
this.http = httpClient;
|
|
68
70
|
this.router = router;
|
|
71
|
+
this.ngZone = ngZone ?? null;
|
|
69
72
|
this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
|
|
70
73
|
// Fetch user once on init. Route changes do NOT re-fetch the user.
|
|
71
74
|
// Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.
|
|
@@ -225,7 +228,12 @@ class MesAuthService {
|
|
|
225
228
|
.configureLogging(LogLevel.Warning);
|
|
226
229
|
this.hubConnection = builder.build();
|
|
227
230
|
this.hubConnection.on('ReceiveNotification', (n) => {
|
|
228
|
-
this.
|
|
231
|
+
if (this.ngZone) {
|
|
232
|
+
this.ngZone.run(() => this._notifications.next(n));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this._notifications.next(n);
|
|
236
|
+
}
|
|
229
237
|
});
|
|
230
238
|
this.hubConnection.start().then(() => { }).catch((err) => { });
|
|
231
239
|
this.hubConnection.onclose(() => { });
|
|
@@ -764,14 +772,20 @@ class NotificationPanelComponent {
|
|
|
764
772
|
currentTheme = 'light';
|
|
765
773
|
activeTab = 'unread'; // Default to unread tab
|
|
766
774
|
destroy$ = new Subject();
|
|
775
|
+
// Cached filtered lists — updated explicitly to avoid re-filtering on every CD cycle
|
|
776
|
+
_unreadNotifications = [];
|
|
777
|
+
_readNotifications = [];
|
|
778
|
+
// Stable time-ago strings keyed by notification id — refreshed every 30s
|
|
779
|
+
dateLabels = new Map();
|
|
780
|
+
dateTimer = null;
|
|
767
781
|
get unreadNotifications() {
|
|
768
|
-
return this.
|
|
782
|
+
return this._unreadNotifications;
|
|
769
783
|
}
|
|
770
784
|
get readNotifications() {
|
|
771
|
-
return this.
|
|
785
|
+
return this._readNotifications;
|
|
772
786
|
}
|
|
773
787
|
get currentNotifications() {
|
|
774
|
-
return this.activeTab === 'unread' ? this.
|
|
788
|
+
return this.activeTab === 'unread' ? this._unreadNotifications : this._readNotifications;
|
|
775
789
|
}
|
|
776
790
|
selectedNotification = null;
|
|
777
791
|
selectedNotificationHtml = null;
|
|
@@ -793,6 +807,8 @@ class NotificationPanelComponent {
|
|
|
793
807
|
this.currentTheme = theme;
|
|
794
808
|
});
|
|
795
809
|
this.loadNotifications();
|
|
810
|
+
// Refresh time-ago labels every 30s to avoid NG0100 from live new Date() in template
|
|
811
|
+
this.dateTimer = setInterval(() => this.refreshDateLabels(), 30000);
|
|
796
812
|
// Listen for new real-time notifications
|
|
797
813
|
this.authService.notifications$
|
|
798
814
|
.pipe(takeUntil(this.destroy$))
|
|
@@ -804,6 +820,10 @@ class NotificationPanelComponent {
|
|
|
804
820
|
});
|
|
805
821
|
}
|
|
806
822
|
ngOnDestroy() {
|
|
823
|
+
if (this.dateTimer !== null) {
|
|
824
|
+
clearInterval(this.dateTimer);
|
|
825
|
+
this.dateTimer = null;
|
|
826
|
+
}
|
|
807
827
|
this.destroy$.next();
|
|
808
828
|
this.destroy$.complete();
|
|
809
829
|
}
|
|
@@ -811,6 +831,7 @@ class NotificationPanelComponent {
|
|
|
811
831
|
this.authService.getNotifications(1, 50, true).subscribe({
|
|
812
832
|
next: (response) => {
|
|
813
833
|
this.notifications = response.items || [];
|
|
834
|
+
this.onNotificationsChanged();
|
|
814
835
|
},
|
|
815
836
|
error: (err) => { }
|
|
816
837
|
});
|
|
@@ -837,6 +858,7 @@ class NotificationPanelComponent {
|
|
|
837
858
|
next: () => {
|
|
838
859
|
notification.isRead = true;
|
|
839
860
|
this.notificationRead.emit();
|
|
861
|
+
this.onNotificationsChanged();
|
|
840
862
|
},
|
|
841
863
|
error: () => { }
|
|
842
864
|
});
|
|
@@ -857,6 +879,7 @@ class NotificationPanelComponent {
|
|
|
857
879
|
if (notification) {
|
|
858
880
|
notification.isRead = true;
|
|
859
881
|
this.notificationRead.emit();
|
|
882
|
+
this.onNotificationsChanged();
|
|
860
883
|
}
|
|
861
884
|
},
|
|
862
885
|
error: (err) => { }
|
|
@@ -867,6 +890,7 @@ class NotificationPanelComponent {
|
|
|
867
890
|
next: () => {
|
|
868
891
|
this.notifications.forEach(n => n.isRead = true);
|
|
869
892
|
this.notificationRead.emit();
|
|
893
|
+
this.onNotificationsChanged();
|
|
870
894
|
},
|
|
871
895
|
error: (err) => { }
|
|
872
896
|
});
|
|
@@ -880,6 +904,7 @@ class NotificationPanelComponent {
|
|
|
880
904
|
Promise.all(deletePromises).then(() => {
|
|
881
905
|
// Remove all read notifications from the local array
|
|
882
906
|
this.notifications = this.notifications.filter(n => !n.isRead);
|
|
907
|
+
this.onNotificationsChanged();
|
|
883
908
|
}).catch((err) => {
|
|
884
909
|
// If bulk delete fails, reload notifications to get current state
|
|
885
910
|
this.loadNotifications();
|
|
@@ -894,6 +919,8 @@ class NotificationPanelComponent {
|
|
|
894
919
|
Promise.all(deletePromises).then(() => {
|
|
895
920
|
// Remove all unread notifications from the local array
|
|
896
921
|
this.notifications = this.notifications.filter(n => n.isRead);
|
|
922
|
+
this.notificationRead.emit();
|
|
923
|
+
this.onNotificationsChanged();
|
|
897
924
|
}).catch((err) => {
|
|
898
925
|
// If bulk delete fails, reload notifications to get current state
|
|
899
926
|
this.loadNotifications();
|
|
@@ -901,22 +928,28 @@ class NotificationPanelComponent {
|
|
|
901
928
|
}
|
|
902
929
|
delete(notificationId, event) {
|
|
903
930
|
event.stopPropagation();
|
|
931
|
+
const wasUnread = this.notifications.find(n => n.id === notificationId && !n.isRead) !== undefined;
|
|
904
932
|
this.authService.deleteNotification(notificationId).subscribe({
|
|
905
933
|
next: () => {
|
|
906
934
|
this.notifications = this.notifications.filter(n => n.id !== notificationId);
|
|
935
|
+
if (wasUnread) {
|
|
936
|
+
this.notificationRead.emit();
|
|
937
|
+
}
|
|
938
|
+
this.onNotificationsChanged();
|
|
907
939
|
},
|
|
908
940
|
error: (err) => { }
|
|
909
941
|
});
|
|
910
942
|
}
|
|
911
943
|
formatDate(dateString) {
|
|
912
|
-
|
|
944
|
+
return this.computeTimeAgo(dateString, new Date());
|
|
945
|
+
}
|
|
946
|
+
// Pure computation — takes now as param so it never calls new Date() internally
|
|
947
|
+
computeTimeAgo(dateString, now) {
|
|
913
948
|
const normalizedDateString = this.parseUtcDate(dateString);
|
|
914
949
|
const date = new Date(normalizedDateString);
|
|
915
|
-
// Check if the date is valid
|
|
916
950
|
if (isNaN(date.getTime())) {
|
|
917
951
|
return 'Invalid date';
|
|
918
952
|
}
|
|
919
|
-
const now = new Date();
|
|
920
953
|
const diffMs = now.getTime() - date.getTime();
|
|
921
954
|
const diffMins = Math.floor(diffMs / 60000);
|
|
922
955
|
const diffHours = Math.floor(diffMs / 3600000);
|
|
@@ -931,6 +964,23 @@ class NotificationPanelComponent {
|
|
|
931
964
|
return `${diffDays}d ago`;
|
|
932
965
|
return date.toLocaleDateString();
|
|
933
966
|
}
|
|
967
|
+
// Rebuild dateLabels map using a single shared now — prevents mid-loop clock drift
|
|
968
|
+
refreshDateLabels() {
|
|
969
|
+
const now = new Date();
|
|
970
|
+
for (const n of this.notifications) {
|
|
971
|
+
this.dateLabels.set(n.id, this.computeTimeAgo(n.createdAt, now));
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Re-run filter once and store results in stable arrays
|
|
975
|
+
recomputeFilteredLists() {
|
|
976
|
+
this._unreadNotifications = this.notifications.filter(n => !n.isRead);
|
|
977
|
+
this._readNotifications = this.notifications.filter(n => n.isRead);
|
|
978
|
+
}
|
|
979
|
+
// Single call-site after every notification mutation
|
|
980
|
+
onNotificationsChanged() {
|
|
981
|
+
this.recomputeFilteredLists();
|
|
982
|
+
this.refreshDateLabels();
|
|
983
|
+
}
|
|
934
984
|
// Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
|
|
935
985
|
parseUtcDate(dateStr) {
|
|
936
986
|
// Handle date strings that might be missing the 'T' separator
|
|
@@ -943,214 +993,214 @@ class NotificationPanelComponent {
|
|
|
943
993
|
return normalized;
|
|
944
994
|
}
|
|
945
995
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
946
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
947
|
-
<div class="notification-panel" [class.open]="isOpen">
|
|
948
|
-
<!-- Header -->
|
|
949
|
-
<div class="panel-header">
|
|
950
|
-
<h3>Notifications</h3>
|
|
951
|
-
<button class="close-btn" (click)="close()" title="Close">✕</button>
|
|
952
|
-
</div>
|
|
953
|
-
|
|
954
|
-
<!-- Tabs -->
|
|
955
|
-
<div class="tabs">
|
|
956
|
-
<button
|
|
957
|
-
class="tab-btn"
|
|
958
|
-
[class.active]="activeTab === 'unread'"
|
|
959
|
-
(click)="switchTab('unread')"
|
|
960
|
-
>
|
|
961
|
-
Unread ({{ unreadNotifications.length }})
|
|
962
|
-
</button>
|
|
963
|
-
<button
|
|
964
|
-
class="tab-btn"
|
|
965
|
-
[class.active]="activeTab === 'read'"
|
|
966
|
-
(click)="switchTab('read')"
|
|
967
|
-
>
|
|
968
|
-
Read ({{ readNotifications.length }})
|
|
969
|
-
</button>
|
|
970
|
-
</div>
|
|
971
|
-
|
|
972
|
-
<!-- Notifications List -->
|
|
973
|
-
<div class="notifications-list">
|
|
974
|
-
<ng-container *ngIf="currentNotifications.length > 0">
|
|
975
|
-
<div
|
|
976
|
-
*ngFor="let notification of currentNotifications"
|
|
977
|
-
class="notification-item"
|
|
978
|
-
[class.unread]="!notification.isRead"
|
|
979
|
-
(click)="openDetails(notification)"
|
|
980
|
-
>
|
|
981
|
-
<div class="notification-content">
|
|
982
|
-
<div class="notification-title">{{ notification.title }}</div>
|
|
983
|
-
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
984
|
-
<div class="notification-meta">
|
|
985
|
-
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
986
|
-
<span class="time">{{
|
|
987
|
-
</div>
|
|
988
|
-
</div>
|
|
989
|
-
<button
|
|
990
|
-
class="read-btn"
|
|
991
|
-
(click)="markAsRead(notification.id, $event)"
|
|
992
|
-
title="Mark as read"
|
|
993
|
-
*ngIf="!notification.isRead"
|
|
994
|
-
>
|
|
995
|
-
✓
|
|
996
|
-
</button>
|
|
997
|
-
<button
|
|
998
|
-
class="delete-btn"
|
|
999
|
-
(click)="delete(notification.id, $event)"
|
|
1000
|
-
title="Delete notification"
|
|
1001
|
-
*ngIf="notification.isRead"
|
|
1002
|
-
>
|
|
1003
|
-
🗑
|
|
1004
|
-
</button>
|
|
1005
|
-
</div>
|
|
1006
|
-
</ng-container>
|
|
1007
|
-
|
|
1008
|
-
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1009
|
-
<div class="empty-state">
|
|
1010
|
-
No {{ activeTab }} notifications
|
|
1011
|
-
</div>
|
|
1012
|
-
</ng-container>
|
|
1013
|
-
</div>
|
|
1014
|
-
|
|
1015
|
-
<!-- Footer Actions -->
|
|
1016
|
-
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1017
|
-
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1018
|
-
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1019
|
-
Mark all as read
|
|
1020
|
-
</button>
|
|
1021
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1022
|
-
Delete all
|
|
1023
|
-
</button>
|
|
1024
|
-
</div>
|
|
1025
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1026
|
-
Delete all
|
|
1027
|
-
</button>
|
|
1028
|
-
</div>
|
|
1029
|
-
</div>
|
|
1030
|
-
|
|
1031
|
-
<!-- Details Modal -->
|
|
1032
|
-
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1033
|
-
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1034
|
-
<div class="modal-header">
|
|
1035
|
-
<h3>{{ selectedNotification.title }}</h3>
|
|
1036
|
-
<button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
|
|
1037
|
-
</div>
|
|
1038
|
-
<div class="modal-meta">
|
|
1039
|
-
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1040
|
-
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1041
|
-
</div>
|
|
1042
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1043
|
-
<div class="modal-footer">
|
|
1044
|
-
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1045
|
-
</div>
|
|
1046
|
-
</div>
|
|
1047
|
-
</div>
|
|
996
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
997
|
+
<div class="notification-panel" [class.open]="isOpen">
|
|
998
|
+
<!-- Header -->
|
|
999
|
+
<div class="panel-header">
|
|
1000
|
+
<h3>Notifications</h3>
|
|
1001
|
+
<button class="close-btn" (click)="close()" title="Close">✕</button>
|
|
1002
|
+
</div>
|
|
1003
|
+
|
|
1004
|
+
<!-- Tabs -->
|
|
1005
|
+
<div class="tabs">
|
|
1006
|
+
<button
|
|
1007
|
+
class="tab-btn"
|
|
1008
|
+
[class.active]="activeTab === 'unread'"
|
|
1009
|
+
(click)="switchTab('unread')"
|
|
1010
|
+
>
|
|
1011
|
+
Unread ({{ unreadNotifications.length }})
|
|
1012
|
+
</button>
|
|
1013
|
+
<button
|
|
1014
|
+
class="tab-btn"
|
|
1015
|
+
[class.active]="activeTab === 'read'"
|
|
1016
|
+
(click)="switchTab('read')"
|
|
1017
|
+
>
|
|
1018
|
+
Read ({{ readNotifications.length }})
|
|
1019
|
+
</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
|
|
1022
|
+
<!-- Notifications List -->
|
|
1023
|
+
<div class="notifications-list">
|
|
1024
|
+
<ng-container *ngIf="currentNotifications.length > 0">
|
|
1025
|
+
<div
|
|
1026
|
+
*ngFor="let notification of currentNotifications"
|
|
1027
|
+
class="notification-item"
|
|
1028
|
+
[class.unread]="!notification.isRead"
|
|
1029
|
+
(click)="openDetails(notification)"
|
|
1030
|
+
>
|
|
1031
|
+
<div class="notification-content">
|
|
1032
|
+
<div class="notification-title">{{ notification.title }}</div>
|
|
1033
|
+
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
1034
|
+
<div class="notification-meta">
|
|
1035
|
+
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
1036
|
+
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
<button
|
|
1040
|
+
class="read-btn"
|
|
1041
|
+
(click)="markAsRead(notification.id, $event)"
|
|
1042
|
+
title="Mark as read"
|
|
1043
|
+
*ngIf="!notification.isRead"
|
|
1044
|
+
>
|
|
1045
|
+
✓
|
|
1046
|
+
</button>
|
|
1047
|
+
<button
|
|
1048
|
+
class="delete-btn"
|
|
1049
|
+
(click)="delete(notification.id, $event)"
|
|
1050
|
+
title="Delete notification"
|
|
1051
|
+
*ngIf="notification.isRead"
|
|
1052
|
+
>
|
|
1053
|
+
🗑
|
|
1054
|
+
</button>
|
|
1055
|
+
</div>
|
|
1056
|
+
</ng-container>
|
|
1057
|
+
|
|
1058
|
+
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1059
|
+
<div class="empty-state">
|
|
1060
|
+
No {{ activeTab }} notifications
|
|
1061
|
+
</div>
|
|
1062
|
+
</ng-container>
|
|
1063
|
+
</div>
|
|
1064
|
+
|
|
1065
|
+
<!-- Footer Actions -->
|
|
1066
|
+
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1067
|
+
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1068
|
+
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1069
|
+
Mark all as read
|
|
1070
|
+
</button>
|
|
1071
|
+
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1072
|
+
Delete all
|
|
1073
|
+
</button>
|
|
1074
|
+
</div>
|
|
1075
|
+
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1076
|
+
Delete all
|
|
1077
|
+
</button>
|
|
1078
|
+
</div>
|
|
1079
|
+
</div>
|
|
1080
|
+
|
|
1081
|
+
<!-- Details Modal -->
|
|
1082
|
+
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1083
|
+
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1084
|
+
<div class="modal-header">
|
|
1085
|
+
<h3>{{ selectedNotification.title }}</h3>
|
|
1086
|
+
<button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
|
|
1087
|
+
</div>
|
|
1088
|
+
<div class="modal-meta">
|
|
1089
|
+
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1090
|
+
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1093
|
+
<div class="modal-footer">
|
|
1094
|
+
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
1097
|
+
</div>
|
|
1048
1098
|
`, 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"] }] });
|
|
1049
1099
|
}
|
|
1050
1100
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1051
1101
|
type: Component,
|
|
1052
|
-
args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
|
|
1053
|
-
<div class="notification-panel" [class.open]="isOpen">
|
|
1054
|
-
<!-- Header -->
|
|
1055
|
-
<div class="panel-header">
|
|
1056
|
-
<h3>Notifications</h3>
|
|
1057
|
-
<button class="close-btn" (click)="close()" title="Close">✕</button>
|
|
1058
|
-
</div>
|
|
1059
|
-
|
|
1060
|
-
<!-- Tabs -->
|
|
1061
|
-
<div class="tabs">
|
|
1062
|
-
<button
|
|
1063
|
-
class="tab-btn"
|
|
1064
|
-
[class.active]="activeTab === 'unread'"
|
|
1065
|
-
(click)="switchTab('unread')"
|
|
1066
|
-
>
|
|
1067
|
-
Unread ({{ unreadNotifications.length }})
|
|
1068
|
-
</button>
|
|
1069
|
-
<button
|
|
1070
|
-
class="tab-btn"
|
|
1071
|
-
[class.active]="activeTab === 'read'"
|
|
1072
|
-
(click)="switchTab('read')"
|
|
1073
|
-
>
|
|
1074
|
-
Read ({{ readNotifications.length }})
|
|
1075
|
-
</button>
|
|
1076
|
-
</div>
|
|
1077
|
-
|
|
1078
|
-
<!-- Notifications List -->
|
|
1079
|
-
<div class="notifications-list">
|
|
1080
|
-
<ng-container *ngIf="currentNotifications.length > 0">
|
|
1081
|
-
<div
|
|
1082
|
-
*ngFor="let notification of currentNotifications"
|
|
1083
|
-
class="notification-item"
|
|
1084
|
-
[class.unread]="!notification.isRead"
|
|
1085
|
-
(click)="openDetails(notification)"
|
|
1086
|
-
>
|
|
1087
|
-
<div class="notification-content">
|
|
1088
|
-
<div class="notification-title">{{ notification.title }}</div>
|
|
1089
|
-
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
1090
|
-
<div class="notification-meta">
|
|
1091
|
-
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
1092
|
-
<span class="time">{{
|
|
1093
|
-
</div>
|
|
1094
|
-
</div>
|
|
1095
|
-
<button
|
|
1096
|
-
class="read-btn"
|
|
1097
|
-
(click)="markAsRead(notification.id, $event)"
|
|
1098
|
-
title="Mark as read"
|
|
1099
|
-
*ngIf="!notification.isRead"
|
|
1100
|
-
>
|
|
1101
|
-
✓
|
|
1102
|
-
</button>
|
|
1103
|
-
<button
|
|
1104
|
-
class="delete-btn"
|
|
1105
|
-
(click)="delete(notification.id, $event)"
|
|
1106
|
-
title="Delete notification"
|
|
1107
|
-
*ngIf="notification.isRead"
|
|
1108
|
-
>
|
|
1109
|
-
🗑
|
|
1110
|
-
</button>
|
|
1111
|
-
</div>
|
|
1112
|
-
</ng-container>
|
|
1113
|
-
|
|
1114
|
-
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1115
|
-
<div class="empty-state">
|
|
1116
|
-
No {{ activeTab }} notifications
|
|
1117
|
-
</div>
|
|
1118
|
-
</ng-container>
|
|
1119
|
-
</div>
|
|
1120
|
-
|
|
1121
|
-
<!-- Footer Actions -->
|
|
1122
|
-
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1123
|
-
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1124
|
-
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1125
|
-
Mark all as read
|
|
1126
|
-
</button>
|
|
1127
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1128
|
-
Delete all
|
|
1129
|
-
</button>
|
|
1130
|
-
</div>
|
|
1131
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1132
|
-
Delete all
|
|
1133
|
-
</button>
|
|
1134
|
-
</div>
|
|
1135
|
-
</div>
|
|
1136
|
-
|
|
1137
|
-
<!-- Details Modal -->
|
|
1138
|
-
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1139
|
-
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1140
|
-
<div class="modal-header">
|
|
1141
|
-
<h3>{{ selectedNotification.title }}</h3>
|
|
1142
|
-
<button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
|
|
1143
|
-
</div>
|
|
1144
|
-
<div class="modal-meta">
|
|
1145
|
-
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1146
|
-
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1147
|
-
</div>
|
|
1148
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1149
|
-
<div class="modal-footer">
|
|
1150
|
-
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1151
|
-
</div>
|
|
1152
|
-
</div>
|
|
1153
|
-
</div>
|
|
1102
|
+
args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
|
|
1103
|
+
<div class="notification-panel" [class.open]="isOpen">
|
|
1104
|
+
<!-- Header -->
|
|
1105
|
+
<div class="panel-header">
|
|
1106
|
+
<h3>Notifications</h3>
|
|
1107
|
+
<button class="close-btn" (click)="close()" title="Close">✕</button>
|
|
1108
|
+
</div>
|
|
1109
|
+
|
|
1110
|
+
<!-- Tabs -->
|
|
1111
|
+
<div class="tabs">
|
|
1112
|
+
<button
|
|
1113
|
+
class="tab-btn"
|
|
1114
|
+
[class.active]="activeTab === 'unread'"
|
|
1115
|
+
(click)="switchTab('unread')"
|
|
1116
|
+
>
|
|
1117
|
+
Unread ({{ unreadNotifications.length }})
|
|
1118
|
+
</button>
|
|
1119
|
+
<button
|
|
1120
|
+
class="tab-btn"
|
|
1121
|
+
[class.active]="activeTab === 'read'"
|
|
1122
|
+
(click)="switchTab('read')"
|
|
1123
|
+
>
|
|
1124
|
+
Read ({{ readNotifications.length }})
|
|
1125
|
+
</button>
|
|
1126
|
+
</div>
|
|
1127
|
+
|
|
1128
|
+
<!-- Notifications List -->
|
|
1129
|
+
<div class="notifications-list">
|
|
1130
|
+
<ng-container *ngIf="currentNotifications.length > 0">
|
|
1131
|
+
<div
|
|
1132
|
+
*ngFor="let notification of currentNotifications"
|
|
1133
|
+
class="notification-item"
|
|
1134
|
+
[class.unread]="!notification.isRead"
|
|
1135
|
+
(click)="openDetails(notification)"
|
|
1136
|
+
>
|
|
1137
|
+
<div class="notification-content">
|
|
1138
|
+
<div class="notification-title">{{ notification.title }}</div>
|
|
1139
|
+
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
1140
|
+
<div class="notification-meta">
|
|
1141
|
+
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
1142
|
+
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1143
|
+
</div>
|
|
1144
|
+
</div>
|
|
1145
|
+
<button
|
|
1146
|
+
class="read-btn"
|
|
1147
|
+
(click)="markAsRead(notification.id, $event)"
|
|
1148
|
+
title="Mark as read"
|
|
1149
|
+
*ngIf="!notification.isRead"
|
|
1150
|
+
>
|
|
1151
|
+
✓
|
|
1152
|
+
</button>
|
|
1153
|
+
<button
|
|
1154
|
+
class="delete-btn"
|
|
1155
|
+
(click)="delete(notification.id, $event)"
|
|
1156
|
+
title="Delete notification"
|
|
1157
|
+
*ngIf="notification.isRead"
|
|
1158
|
+
>
|
|
1159
|
+
🗑
|
|
1160
|
+
</button>
|
|
1161
|
+
</div>
|
|
1162
|
+
</ng-container>
|
|
1163
|
+
|
|
1164
|
+
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1165
|
+
<div class="empty-state">
|
|
1166
|
+
No {{ activeTab }} notifications
|
|
1167
|
+
</div>
|
|
1168
|
+
</ng-container>
|
|
1169
|
+
</div>
|
|
1170
|
+
|
|
1171
|
+
<!-- Footer Actions -->
|
|
1172
|
+
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1173
|
+
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1174
|
+
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1175
|
+
Mark all as read
|
|
1176
|
+
</button>
|
|
1177
|
+
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1178
|
+
Delete all
|
|
1179
|
+
</button>
|
|
1180
|
+
</div>
|
|
1181
|
+
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1182
|
+
Delete all
|
|
1183
|
+
</button>
|
|
1184
|
+
</div>
|
|
1185
|
+
</div>
|
|
1186
|
+
|
|
1187
|
+
<!-- Details Modal -->
|
|
1188
|
+
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1189
|
+
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1190
|
+
<div class="modal-header">
|
|
1191
|
+
<h3>{{ selectedNotification.title }}</h3>
|
|
1192
|
+
<button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
|
|
1193
|
+
</div>
|
|
1194
|
+
<div class="modal-meta">
|
|
1195
|
+
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1196
|
+
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1197
|
+
</div>
|
|
1198
|
+
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1199
|
+
<div class="modal-footer">
|
|
1200
|
+
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1201
|
+
</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>
|
|
1154
1204
|
`, 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"] }]
|
|
1155
1205
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
|
|
1156
1206
|
type: Output
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mesauth-angular.mjs","sources":["../../src/mes-auth.service.ts","../../src/mes-auth.interceptor.ts","../../src/mes-auth.module.ts","../../src/theme.service.ts","../../src/user-profile.component.ts","../../src/toast.service.ts","../../src/toast-container.component.ts","../../src/notification-panel.component.ts","../../src/ma-user.component.ts","../../src/notification-badge.component.ts","../../src/mesauth-angular.ts"],"sourcesContent":["import { inject, Injectable, InjectionToken, EnvironmentProviders, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\r\nimport { BehaviorSubject, Subject, Observable, of, EMPTY } from 'rxjs';\r\nimport { tap, catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\n\r\nexport interface MesAuthConfig {\r\n apiBaseUrl: string;\r\n withCredentials?: boolean;\r\n userBaseUrl?: string;\r\n}\r\n\r\n/** Injection token for MesAuth configuration */\r\nexport const MES_AUTH_CONFIG = new InjectionToken<MesAuthConfig>('MES_AUTH_CONFIG');\r\n\r\n/**\r\n * Provides MesAuth with configuration.\r\n * This is the recommended way to set up mesauth-angular in standalone apps.\r\n *\r\n * @example\r\n * ```typescript\r\n * // app.config.ts\r\n * export const appConfig: ApplicationConfig = {\r\n * providers: [\r\n * provideHttpClient(withInterceptors([mesAuthInterceptor])),\r\n * provideMesAuth({\r\n * apiBaseUrl: 'https://auth.example.com',\r\n * userBaseUrl: 'https://app.example.com'\r\n * })\r\n * ]\r\n * };\r\n * ```\r\n */\r\nexport function provideMesAuth(config: MesAuthConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: MES_AUTH_CONFIG, useValue: config },\r\n MesAuthService,\r\n provideAppInitializer(() => {\r\n const mesAuthService = inject(MesAuthService);\r\n const httpClient = inject(HttpClient);\r\n const router = inject(Router);\r\n mesAuthService.init(config, httpClient, router);\r\n })\r\n ]);\r\n}\r\n\r\nexport interface IUser {\r\n userId?: string;\r\n userName?: string;\r\n fullName?: string;\r\n gender?: string;\r\n email?: string;\r\n phoneNumber?: string;\r\n department?: string;\r\n position?: string;\r\n tokenVersion?: string;\r\n permEndpoint?: string;\r\n perms?: Set<string>;\r\n employeeCode?: string;\r\n avatarPath?: string;\r\n loginMethod?: number;\r\n hrFullNameVn?: string;\r\n hrFullNameEn?: string;\r\n hrPosition?: string;\r\n hrJobTitle?: string;\r\n hrGender?: string;\r\n hrMobile?: string;\r\n hrEmail?: string;\r\n hrJoinDate?: string;\r\n hrBirthDate?: string;\r\n hrWorkStatus?: string;\r\n hrDoiTuong?: string;\r\n hrTeamCode?: string;\r\n hrLineCode?: string;\r\n}\r\n\r\nexport enum NotificationType {\r\n Info = 'Info',\r\n Warning = 'Warning',\r\n Error = 'Error',\r\n Success = 'Success'\r\n}\r\n\r\nexport interface NotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n isRead: boolean;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\nexport interface FrontEndRoute {\r\n id: number;\r\n roleId: string;\r\n roleName: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n children: FrontEndRoute[];\r\n}\r\n\r\nexport interface UserFrontEndRoutesGrouped {\r\n appId: string;\r\n appName: string;\r\n feUrl?: string;\r\n routes: FrontEndRoute[];\r\n}\r\n\r\nexport interface FrontEndRouteMaster {\r\n id: number;\r\n appId: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n}\r\n\r\nexport interface CreateFrontEndRouteDto {\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder?: number;\r\n isLabel?: boolean;\r\n}\r\n\r\nexport interface PagedList<T> {\r\n items: T[];\r\n totalCount: number;\r\n page: number;\r\n pageSize: number;\r\n totalPages: number;\r\n hasNext: boolean;\r\n hasPrevious: boolean;\r\n}\r\n\r\nexport interface RealTimeNotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\n@Injectable()\r\nexport class MesAuthService {\r\n private hubConnection: HubConnection | null = null;\r\n private _currentUser = new BehaviorSubject<IUser | null>(null);\r\n public currentUser$: Observable<IUser | null> = this._currentUser.asObservable();\r\n private _notifications = new Subject<any>();\r\n public notifications$: Observable<any> = this._notifications.asObservable();\r\n\r\n private apiBase = '';\r\n private config: MesAuthConfig | null = null;\r\n private http!: HttpClient;\r\n private router?: Router;\r\n\r\n constructor() {\r\n // Empty constructor - all dependencies passed to init()\r\n }\r\n\r\n init(config: MesAuthConfig, httpClient: HttpClient, router?: Router) {\r\n this.config = config;\r\n this.http = httpClient;\r\n this.router = router;\r\n this.apiBase = config.apiBaseUrl.replace(/\\/$/, '');\r\n\r\n // Fetch user once on init. Route changes do NOT re-fetch the user.\r\n // Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.\r\n // SignalR handles real-time notification delivery without polling.\r\n this.fetchCurrentUser().subscribe();\r\n }\r\n\r\n getConfig(): MesAuthConfig | null {\r\n return this.config;\r\n }\r\n\r\n private fetchCurrentUser(): Observable<any> {\r\n if (!this.apiBase) return EMPTY;\r\n const url = `${this.apiBase}/auth/me`;\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap((u) => {\r\n this._currentUser.next(u);\r\n if (u && this.config) {\r\n this.startConnection(this.config);\r\n }\r\n }),\r\n catchError((err) => {\r\n // Silently handle auth errors (401/403) - user is not logged in\r\n if (err.status === 401 || err.status === 403) {\r\n this._currentUser.next(null);\r\n }\r\n return of(null);\r\n })\r\n );\r\n }\r\n\r\n public getUnreadCount(): Observable<any> {\r\n return this.http.get(`${this.apiBase}/notif/me/unread-count`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public getNotifications(page: number = 1, pageSize: number = 20, includeRead: boolean = false, type?: string): Observable<any> {\r\n let url = `${this.apiBase}/notif/me?page=${page}&pageSize=${pageSize}&includeRead=${includeRead}`;\r\n if (type) {\r\n url += `&type=${type}`;\r\n }\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAsRead(notificationId: string): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/${notificationId}/read`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAllAsRead(): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/me/read-all`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public deleteNotification(notificationId: string): Observable<any> {\r\n return this.http.delete(`${this.apiBase}/notif/${notificationId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get frontend routes assigned to the current user\r\n * Returns routes grouped by application\r\n */\r\n public getFrontEndRoutes(): Observable<UserFrontEndRoutesGrouped[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<UserFrontEndRoutesGrouped[]>(`${this.apiBase}/fe-routes/me`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get master routes for a specific application\r\n * @param appId - The application ID\r\n */\r\n public getRouteMasters(appId: string): Observable<FrontEndRouteMaster[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<FrontEndRouteMaster[]>(`${this.apiBase}/fe-routes/masters/${appId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Register/sync frontend routes for an application\r\n * This is typically called on app startup to sync routes from the frontend app\r\n * @param appId - The application ID (passed via X-App-Id header)\r\n * @param routes - Array of route definitions\r\n */\r\n public registerFrontEndRoutes(appId: string, routes: CreateFrontEndRouteDto[]): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n const headers = { 'X-App-Id': appId };\r\n return this.http.post(`${this.apiBase}/fe-routes/register`, routes, {\r\n headers,\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Create a new route master\r\n * @param appId - The application ID\r\n * @param route - Route details\r\n */\r\n public createRouteMaster(appId: string, route: CreateFrontEndRouteDto): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters`, {\r\n appId,\r\n ...route\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Update an existing route master\r\n * @param routeId - The route master ID\r\n * @param route - Updated route details\r\n */\r\n public updateRouteMaster(routeId: number, route: Partial<CreateFrontEndRouteDto> & { isActive?: boolean }): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.put<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters/${routeId}`, route, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Delete a route master\r\n * @param routeId - The route master ID\r\n */\r\n public deleteRouteMaster(routeId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/masters/${routeId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Assign a route to a role\r\n * @param routeMasterId - The route master ID\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public assignRouteToRole(routeMasterId: number, roleId: string): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post(`${this.apiBase}/fe-routes/mappings`, {\r\n routeMasterId,\r\n roleId\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Remove a route assignment from a role\r\n * @param mappingId - The mapping ID\r\n */\r\n public removeRouteFromRole(mappingId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/mappings/${mappingId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Get route-to-role mappings for a specific role\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public getRouteMappingsByRole(roleId: string): Observable<any[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<any[]>(`${this.apiBase}/fe-routes/mappings?roleId=${roleId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n private startConnection(config: MesAuthConfig) {\r\n if (this.hubConnection) return;\r\n const signalrUrl = config.apiBaseUrl.replace(/\\/$/, '') + '/hub/notification';\r\n const builder = new HubConnectionBuilder()\r\n .withUrl(signalrUrl, { withCredentials: config.withCredentials ?? true })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning);\r\n\r\n this.hubConnection = builder.build();\r\n\r\n this.hubConnection.on('ReceiveNotification', (n: any) => {\r\n this._notifications.next(n);\r\n });\r\n\r\n this.hubConnection.start().then(() => {}).catch((err) => {});\r\n\r\n this.hubConnection.onclose(() => {});\r\n this.hubConnection.onreconnecting(() => {});\r\n this.hubConnection.onreconnected(() => {});\r\n }\r\n\r\n public stop() {\r\n if (!this.hubConnection) return;\r\n this.hubConnection.stop().catch(() => {});\r\n this.hubConnection = null;\r\n }\r\n\r\n public logout(): Observable<any> {\r\n const url = `${this.apiBase}/auth/logout`;\r\n return this.http.post(url, {}, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap(() => {\r\n this._currentUser.next(null);\r\n this.stop();\r\n })\r\n );\r\n }\r\n\r\n public get currentUser(): IUser | null {\r\n return this._currentUser.value;\r\n }\r\n\r\n public get isAuthenticated(): boolean {\r\n return this._currentUser.value !== null;\r\n }\r\n\r\n /**\r\n * Refreshes the current user from the server.\r\n * Returns an Observable that completes when the user data is loaded.\r\n * Callers can subscribe to wait for completion before proceeding (e.g., navigating after login).\r\n */\r\n public refreshUser(): Observable<any> {\r\n return this.fetchCurrentUser();\r\n }\r\n}\r\n","import { inject } from '@angular/core';\r\nimport { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';\r\nimport { throwError } from 'rxjs';\r\nimport { catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n// Track if we're currently redirecting to prevent loopback\r\nlet isRedirecting = false;\r\n\r\n/**\r\n * Functional HTTP interceptor for handling 401/403 auth errors.\r\n * Redirects to login page on 401, and to 403 page on 403.\r\n * Includes loopback prevention to avoid infinite redirects.\r\n */\r\nexport const mesAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const authService = inject(MesAuthService);\r\n const router = inject(Router);\r\n\r\n return next(req).pipe(\r\n catchError((error: HttpErrorResponse) => {\r\n const status = error.status;\r\n\r\n // Check if we should handle this error and prevent loopback\r\n if ((status === 401 || status === 403) && !isRedirecting) {\r\n const config = authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n\r\n const currentUrl = router.url + (window.location.hash || '');\r\n const returnUrl = encodeURIComponent(currentUrl);\r\n\r\n // Avoid loops if already on auth/unauth pages\r\n const isLoginPage = currentUrl.includes('/login');\r\n const is403Page = currentUrl.includes('/403');\r\n const isAuthPage = currentUrl.includes('/auth');\r\n // Public pages that should never trigger a 401 redirect (e.g., register, password reset)\r\n const isPublicPage = currentUrl.includes('/register')\r\n || currentUrl.includes('/forgot-password')\r\n || currentUrl.includes('/reset-password');\r\n // Skip redirect for the initial /auth/me check (app startup when not logged in)\r\n const isMeAuthPage = req.url.includes('/auth/me');\r\n\r\n if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage && !isPublicPage) {\r\n // Session expired or not authenticated - redirect to login\r\n // No isAuthenticated check: when session expires, BehaviorSubject still holds\r\n // stale user data, so checking isAuthenticated would block the redirect.\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n } else if (status === 403 && !is403Page) {\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n let redirectUrl = `${baseUrl}/403?returnUrl=${returnUrl}`;\r\n if (error.error && error.error.required) {\r\n redirectUrl += `&required=${encodeURIComponent(error.error.required)}`;\r\n }\r\n window.location.href = redirectUrl;\r\n }\r\n }\r\n return throwError(() => error);\r\n })\r\n );\r\n};\r\n","import { NgModule } from '@angular/core';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n@NgModule({\r\n providers: [\r\n MesAuthService\r\n ]\r\n})\r\nexport class MesAuthModule {}\r\n","import { Injectable, OnDestroy } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport type Theme = 'light' | 'dark';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ThemeService implements OnDestroy {\r\n private _currentTheme = new BehaviorSubject<Theme>('light');\r\n public currentTheme$: Observable<Theme> = this._currentTheme.asObservable();\r\n private observer: MutationObserver | null = null;\r\n\r\n constructor() {\r\n this.detectTheme();\r\n this.startWatching();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.stopWatching();\r\n }\r\n\r\n private detectTheme(): void {\r\n const html = document.documentElement;\r\n const isDark = html.classList.contains('dark') ||\r\n html.getAttribute('data-theme') === 'dark' ||\r\n html.getAttribute('theme') === 'dark' ||\r\n html.getAttribute('data-coreui-theme') === 'dark';\r\n\r\n this._currentTheme.next(isDark ? 'dark' : 'light');\r\n }\r\n\r\n private startWatching(): void {\r\n if (typeof MutationObserver === 'undefined') {\r\n // Fallback for older browsers - check periodically\r\n setInterval(() => this.detectTheme(), 1000);\r\n return;\r\n }\r\n\r\n this.observer = new MutationObserver(() => {\r\n this.detectTheme();\r\n });\r\n\r\n this.observer.observe(document.documentElement, {\r\n attributes: true,\r\n attributeFilter: ['class', 'data-theme', 'theme', 'data-coreui-theme']\r\n });\r\n }\r\n\r\n private stopWatching(): void {\r\n if (this.observer) {\r\n this.observer.disconnect();\r\n this.observer = null;\r\n }\r\n }\r\n\r\n get currentTheme(): Theme {\r\n return this._currentTheme.value;\r\n }\r\n\r\n // Method to manually set theme if needed\r\n setTheme(theme: Theme): void {\r\n this._currentTheme.next(theme);\r\n }\r\n\r\n // Re-detect theme from DOM\r\n refreshTheme(): void {\r\n this.detectTheme();\r\n }\r\n}","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding, HostListener, signal, ChangeDetectorRef } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService, IUser } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-user-profile',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <div class=\"user-profile-container\">\r\n <!-- Not logged in -->\r\n <ng-container *ngIf=\"!currentUser()\">\r\n <button class=\"login-btn\" (click)=\"onLogin()\">\r\n Login\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Logged in -->\r\n <ng-container *ngIf=\"currentUser()\">\r\n <div class=\"user-header\">\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n\r\n <div class=\"user-menu-wrapper\">\r\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\">\r\n <img \r\n *ngIf=\"currentUser().fullName || currentUser().userName\"\r\n [src]=\"getAvatarUrl(currentUser())\" \r\n [alt]=\"currentUser().fullName || currentUser().userName\"\r\n class=\"avatar\"\r\n />\r\n <span *ngIf=\"!(currentUser().fullName || currentUser().userName)\" class=\"avatar-initial\">\r\n {{ getLastNameInitial(currentUser()) }}\r\n </span>\r\n </button>\r\n\r\n <div class=\"mes-dropdown-menu\" *ngIf=\"dropdownOpen\">\r\n <div class=\"mes-dropdown-header\">\r\n {{ currentUser().fullName || currentUser().userName }}\r\n </div>\r\n <button class=\"mes-dropdown-item profile-link\" (click)=\"onViewProfile()\">\r\n View Profile\r\n </button>\r\n <button class=\"mes-dropdown-item logout-item\" (click)=\"onLogout()\">\r\n Logout\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --primary-color: #1976d2;\r\n --primary-hover: #1565c0;\r\n --primary-light: rgba(25, 118, 210, 0.1);\r\n --error-color: #f44336;\r\n --error-light: #ffebee;\r\n --text-primary: #333;\r\n --text-secondary: #666;\r\n --text-muted: #999;\r\n --bg-primary: white;\r\n --bg-secondary: #f5f5f5;\r\n --bg-tertiary: #fafafa;\r\n --bg-hover: #f5f5f5;\r\n --border-color: #e0e0e0;\r\n --border-light: #f0f0f0;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --shadow-light: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --primary-color: #90caf9;\r\n --primary-hover: #64b5f6;\r\n --primary-light: rgba(144, 202, 249, 0.1);\r\n --error-color: #ef5350;\r\n --error-light: rgba(239, 83, 80, 0.1);\r\n --text-primary: #e0e0e0;\r\n --text-secondary: #b0b0b0;\r\n --text-muted: #888;\r\n --bg-primary: #1e1e1e;\r\n --bg-secondary: #2d2d2d;\r\n --bg-tertiary: #252525;\r\n --bg-hover: #333;\r\n --border-color: #404040;\r\n --border-light: #333;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --shadow-light: rgba(0, 0, 0, 0.2);\r\n }\r\n\r\n .user-profile-container {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n padding: 0 16px;\r\n }\r\n\r\n .login-btn {\r\n padding: 8px 16px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n transition: background-color 0.3s;\r\n }\r\n\r\n .login-btn:hover {\r\n background-color: var(--primary-hover);\r\n }\r\n\r\n .user-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n\r\n .user-menu-wrapper {\r\n position: relative;\r\n }\r\n\r\n .user-menu-btn {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 50%;\r\n transition: background-color 0.2s;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n\r\n .user-menu-btn:hover {\r\n background-color: var(--primary-light);\r\n }\r\n\r\n .avatar {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n object-fit: cover;\r\n background-color: #e0e0e0;\r\n }\r\n\r\n .avatar-initial {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n background-color: var(--primary-color);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-weight: bold;\r\n font-size: 16px;\r\n }\r\n\r\n .mes-dropdown-menu {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n box-shadow: 0 2px 8px var(--shadow);\r\n min-width: 200px;\r\n z-index: 1000;\r\n overflow: hidden;\r\n }\r\n\r\n .mes-dropdown-header {\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--border-light);\r\n font-weight: 600;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n }\r\n\r\n .mes-dropdown-item {\r\n display: block;\r\n width: 100%;\r\n padding: 12px 16px;\r\n border: none;\r\n background: none;\r\n text-align: left;\r\n cursor: pointer;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n text-decoration: none;\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .mes-dropdown-item:hover {\r\n background-color: var(--bg-hover);\r\n }\r\n\r\n .profile-link {\r\n color: var(--primary-color);\r\n }\r\n\r\n .logout-item {\r\n border-top: 1px solid var(--border-light);\r\n color: var(--error-color);\r\n }\r\n\r\n .logout-item:hover {\r\n background-color: var(--error-light);\r\n }\r\n\r\n .user-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n }\r\n\r\n .user-name {\r\n font-weight: 500;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .user-position {\r\n font-size: 12px;\r\n color: var(--text-secondary);\r\n }\r\n\r\n .logout-btn {\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n color: var(--text-secondary);\r\n padding: 4px 8px;\r\n transition: color 0.2s;\r\n }\r\n\r\n .logout-btn:hover {\r\n color: var(--primary-color);\r\n }\r\n\r\n @media (max-width: 768px) {\r\n .user-info {\r\n display: none;\r\n }\r\n\r\n .avatar {\r\n width: 32px;\r\n height: 32px;\r\n }\r\n }\r\n `]\r\n})\r\nexport class UserProfileComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n currentUser = signal<IUser | null>(null);\r\n currentTheme: Theme = 'light';\r\n unreadCount = 0;\r\n dropdownOpen = false;\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n // Signal to force avatar refresh\r\n avatarRefresh = signal<number>(Date.now());\r\n\r\n constructor(private authService: MesAuthService, private router: Router, private themeService: ThemeService, private cdr: ChangeDetectorRef) {}\r\n\r\n ngOnInit() {\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.currentUser.set(user);\r\n this.hasUser = !!user;\r\n // Force avatar refresh when user changes\r\n this.avatarRefresh.set(Date.now());\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n } else {\r\n this.loadUnreadCount();\r\n }\r\n this.cdr.markForCheck();\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n // Listen for new real-time notifications (SignalR only)\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n getAvatarUrl(user: IUser): string {\r\n // Use the refresh signal to force update\r\n const refresh = this.avatarRefresh();\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.apiBaseUrl || '';\r\n \r\n // If user has avatarPath, use it directly\r\n if (user.avatarPath) {\r\n // If avatarPath is already a full URL, use it as-is\r\n if (user.avatarPath.startsWith('http://') || user.avatarPath.startsWith('https://')) {\r\n return user.avatarPath;\r\n }\r\n // If it's a relative path, construct full URL with refresh timestamp\r\n return `${baseUrl.replace(/\\/$/, '')}${user.avatarPath}?t=${refresh}`;\r\n }\r\n\r\n // Fallback: construct URL using userId\r\n const userId = user.userId;\r\n if (userId && baseUrl) {\r\n return `${baseUrl.replace(/\\/$/, '')}/auth/${userId}/avatar?t=${refresh}`;\r\n }\r\n \r\n // Fallback to UI avatars service if no userId or baseUrl\r\n const displayName = user.userName || user.userId || 'User';\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;\r\n }\r\n\r\n getLastNameInitial(user: IUser): string {\r\n const fullName = user.fullName || user.userName || 'U';\r\n const parts = fullName.split(' ');\r\n const lastPart = parts[parts.length - 1];\r\n return lastPart.charAt(0).toUpperCase();\r\n }\r\n\r\n toggleDropdown() {\r\n this.dropdownOpen = !this.dropdownOpen;\r\n }\r\n\r\n @HostListener('document:click', ['$event'])\r\n onDocumentClick(event: Event) {\r\n const target = event.target as HTMLElement;\r\n const clickedInside = target.closest('.user-menu-wrapper');\r\n if (!clickedInside) {\r\n this.dropdownOpen = false;\r\n }\r\n }\r\n\r\n onLogin() {\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(this.router.url);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n }\r\n\r\n onViewProfile() {\r\n this.router.navigate(['/profile']);\r\n this.dropdownOpen = false;\r\n }\r\n\r\n onLogout() {\r\n this.authService.logout().subscribe({\r\n next: () => {\r\n // Clear current user after successful logout\r\n this.dropdownOpen = false;\r\n \r\n // Navigate to login with return URL\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(window.location.href);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n },\r\n error: (err) => {\r\n // Still navigate to login even if logout fails\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n window.location.href = `${baseUrl}/login`;\r\n }\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n\r\n","import { Injectable } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport interface Toast {\r\n id: string;\r\n message: string;\r\n title?: string;\r\n type: 'info' | 'success' | 'warning' | 'error';\r\n duration?: number;\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ToastService {\r\n private toasts$ = new BehaviorSubject<Toast[]>([]);\r\n public toasts: Observable<Toast[]> = this.toasts$.asObservable();\r\n\r\n show(message: string, title?: string, type: 'info' | 'success' | 'warning' | 'error' = 'info', duration: number = 5000) {\r\n const id = Math.random().toString(36).substr(2, 9);\r\n const toast: Toast = {\r\n id,\r\n message,\r\n title,\r\n type,\r\n duration\r\n };\r\n\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next([...currentToasts, toast]);\r\n\r\n if (duration > 0) {\r\n setTimeout(() => {\r\n this.remove(id);\r\n }, duration);\r\n }\r\n\r\n return id;\r\n }\r\n\r\n remove(id: string) {\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next(currentToasts.filter(t => t.id !== id));\r\n }\r\n\r\n clear() {\r\n this.toasts$.next([]);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { ToastService, Toast } from './toast.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-toast-container',\r\n standalone: true,\r\n imports: [CommonModule],\r\n template: `\r\n <div class=\"toast-container\">\r\n <div \r\n *ngFor=\"let toast of toasts\"\r\n class=\"toast\"\r\n [class]=\"'toast-' + toast.type\"\r\n [@slideIn]\r\n >\r\n <div class=\"toast-content\">\r\n <div *ngIf=\"toast.title\" class=\"toast-title\">{{ toast.title }}</div>\r\n <div class=\"toast-message\" [innerHTML]=\"toast.message\"></div>\r\n </div>\r\n <button class=\"toast-close\" (click)=\"close(toast.id)\" aria-label=\"Close\">\r\n ✕\r\n </button>\r\n </div>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --info-color: #2196f3;\r\n --success-color: #4caf50;\r\n --warning-color: #ff9800;\r\n --error-color: #f44336;\r\n --text-primary: #333;\r\n --bg-primary: white;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --text-secondary: #999;\r\n --border-color: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --info-color: #64b5f6;\r\n --success-color: #81c784;\r\n --warning-color: #ffb74d;\r\n --error-color: #ef5350;\r\n --text-primary: #e0e0e0;\r\n --bg-primary: #1e1e1e;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --text-secondary: #888;\r\n --border-color: rgba(255, 255, 255, 0.1);\r\n }\r\n\r\n .toast-container {\r\n position: fixed;\r\n top: 20px;\r\n right: 20px;\r\n z-index: 9999;\r\n pointer-events: none;\r\n }\r\n\r\n .toast {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n margin-bottom: 12px;\r\n border-radius: 4px;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n box-shadow: 0 4px 12px var(--shadow);\r\n pointer-events: auto;\r\n min-width: 280px;\r\n max-width: 400px;\r\n animation: slideIn 0.3s ease-out;\r\n }\r\n\r\n .toast-content {\r\n flex: 1;\r\n }\r\n\r\n .toast-title {\r\n font-weight: 600;\r\n font-size: 14px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n .toast-message {\r\n font-size: 13px;\r\n line-height: 1.4;\r\n }\r\n\r\n .toast-close {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 18px;\r\n color: var(--text-secondary);\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .toast-close:hover {\r\n color: var(--text-primary);\r\n }\r\n\r\n /* Toast types */\r\n .toast-info {\r\n border-left: 4px solid var(--info-color);\r\n }\r\n\r\n .toast-info .toast-title {\r\n color: var(--info-color);\r\n }\r\n\r\n .toast-info .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-success {\r\n border-left: 4px solid var(--success-color);\r\n }\r\n\r\n .toast-success .toast-title {\r\n color: var(--success-color);\r\n }\r\n\r\n .toast-success .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-warning {\r\n border-left: 4px solid var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-title {\r\n color: var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-error {\r\n border-left: 4px solid var(--error-color);\r\n }\r\n\r\n .toast-error .toast-title {\r\n color: var(--error-color);\r\n }\r\n\r\n .toast-error .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n @keyframes slideIn {\r\n from {\r\n transform: translateX(400px);\r\n opacity: 0;\r\n }\r\n to {\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n }\r\n\r\n @media (max-width: 600px) {\r\n .toast-container {\r\n top: 10px;\r\n right: 10px;\r\n left: 10px;\r\n }\r\n\r\n .toast {\r\n min-width: auto;\r\n max-width: 100%;\r\n }\r\n }\r\n `]\r\n})\r\nexport class ToastContainerComponent implements OnInit, OnDestroy {\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n toasts: Toast[] = [];\r\n currentTheme: Theme = 'light';\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private toastService: ToastService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.toastService.toasts\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(toasts => {\r\n this.toasts = toasts;\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n close(id: string) {\r\n this.toastService.remove(id);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding, Output, EventEmitter, inject } from '@angular/core';\r\nimport { NgIf, NgFor } from '@angular/common';\r\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\r\nimport { MesAuthService, NotificationDto, PagedList, RealTimeNotificationDto } from './mes-auth.service';\r\nimport { ToastService } from './toast.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-notification-panel',\r\n standalone: true,\r\n imports: [NgIf, NgFor],\r\n template: `\r\n <div class=\"notification-panel\" [class.open]=\"isOpen\">\r\n <!-- Header -->\r\n <div class=\"panel-header\">\r\n <h3>Notifications</h3>\r\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\">✕</button>\r\n </div>\r\n\r\n <!-- Tabs -->\r\n <div class=\"tabs\">\r\n <button\r\n class=\"tab-btn\"\r\n [class.active]=\"activeTab === 'unread'\"\r\n (click)=\"switchTab('unread')\"\r\n >\r\n Unread ({{ unreadNotifications.length }})\r\n </button>\r\n <button\r\n class=\"tab-btn\"\r\n [class.active]=\"activeTab === 'read'\"\r\n (click)=\"switchTab('read')\"\r\n >\r\n Read ({{ readNotifications.length }})\r\n </button>\r\n </div>\r\n\r\n <!-- Notifications List -->\r\n <div class=\"notifications-list\">\r\n <ng-container *ngIf=\"currentNotifications.length > 0\">\r\n <div\r\n *ngFor=\"let notification of currentNotifications\"\r\n class=\"notification-item\"\r\n [class.unread]=\"!notification.isRead\"\r\n (click)=\"openDetails(notification)\"\r\n >\r\n <div class=\"notification-content\">\r\n <div class=\"notification-title\">{{ notification.title }}</div>\r\n <div class=\"notification-message\">{{ getNotificationMessage(notification) }}</div>\r\n <div class=\"notification-meta\">\r\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\r\n <span class=\"time\">{{ formatDate(notification.createdAt) }}</span>\r\n </div>\r\n </div>\r\n <button\r\n class=\"read-btn\"\r\n (click)=\"markAsRead(notification.id, $event)\"\r\n title=\"Mark as read\"\r\n *ngIf=\"!notification.isRead\"\r\n >\r\n ✓\r\n </button>\r\n <button\r\n class=\"delete-btn\"\r\n (click)=\"delete(notification.id, $event)\"\r\n title=\"Delete notification\"\r\n *ngIf=\"notification.isRead\"\r\n >\r\n 🗑\r\n </button>\r\n </div>\r\n </ng-container>\r\n\r\n <ng-container *ngIf=\"currentNotifications.length === 0\">\r\n <div class=\"empty-state\">\r\n No {{ activeTab }} notifications\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Footer Actions -->\r\n <div class=\"panel-footer\" *ngIf=\"currentNotifications.length > 0\">\r\n <div class=\"footer-actions\" *ngIf=\"activeTab === 'unread'\">\r\n <button class=\"action-btn\" (click)=\"markAllAsRead()\" *ngIf=\"unreadNotifications.length > 0\">\r\n Mark all as read\r\n </button>\r\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\" *ngIf=\"unreadNotifications.length > 0\">\r\n Delete all\r\n </button>\r\n </div>\r\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\" *ngIf=\"activeTab === 'read' && readNotifications.length > 0\">\r\n Delete all\r\n </button>\r\n </div>\r\n </div>\r\n\r\n <!-- Details Modal -->\r\n <div class=\"modal-overlay\" *ngIf=\"selectedNotification\" (click)=\"closeDetails()\">\r\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\r\n <div class=\"modal-header\">\r\n <h3>{{ selectedNotification.title }}</h3>\r\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\">✕</button>\r\n </div>\r\n <div class=\"modal-meta\">\r\n <span class=\"app-name\">{{ selectedNotification.sourceAppName }}</span>\r\n <span class=\"time\">{{ selectedNotificationDate }}</span>\r\n </div>\r\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml\"></div>\r\n <div class=\"modal-footer\">\r\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\r\n </div>\r\n </div>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n display: block;\r\n position: relative;\r\n --primary-color: #1976d2;\r\n --primary-hover: #1565c0;\r\n --success-color: #4caf50;\r\n --error-color: #f44336;\r\n --text-primary: #333;\r\n --text-secondary: #666;\r\n --text-muted: #999;\r\n --bg-primary: white;\r\n --bg-secondary: #f5f5f5;\r\n --bg-tertiary: #fafafa;\r\n --bg-hover: #f5f5f5;\r\n --bg-unread: #e3f2fd;\r\n --border-color: #e0e0e0;\r\n --border-light: #f0f0f0;\r\n --shadow: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n display: block;\r\n position: relative;\r\n --primary-color: #90caf9;\r\n --primary-hover: #64b5f6;\r\n --success-color: #81c784;\r\n --error-color: #ef5350;\r\n --text-primary: #e0e0e0;\r\n --text-secondary: #b0b0b0;\r\n --text-muted: #888;\r\n --bg-primary: #1e1e1e;\r\n --bg-secondary: #2d2d2d;\r\n --bg-tertiary: #252525;\r\n --bg-hover: #333;\r\n --bg-unread: rgba(144, 202, 249, 0.1);\r\n --border-color: #404040;\r\n --border-light: #333;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n }\r\n\r\n .notification-panel {\r\n position: fixed;\r\n top: 0;\r\n right: -350px;\r\n width: 350px;\r\n height: 100vh;\r\n background: var(--bg-primary);\r\n box-shadow: -2px 0 8px var(--shadow);\r\n display: flex;\r\n flex-direction: column;\r\n z-index: 1030;\r\n transition: right 0.3s ease;\r\n }\r\n\r\n .notification-panel.open {\r\n right: 0;\r\n }\r\n\r\n .panel-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .panel-header h3 {\r\n margin: 0;\r\n font-size: 18px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .close-btn {\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n color: var(--text-secondary);\r\n padding: 0;\r\n width: 32px;\r\n height: 32px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n transition: color 0.2s;\r\n }\r\n\r\n .close-btn:hover {\r\n color: var(--text-primary);\r\n }\r\n\r\n .tabs {\r\n display: flex;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .tab-btn {\r\n flex: 1;\r\n padding: 12px 16px;\r\n background: none;\r\n border: none;\r\n color: var(--text-secondary);\r\n cursor: pointer;\r\n font-size: 14px;\r\n font-weight: 500;\r\n transition: all 0.2s;\r\n border-bottom: 2px solid transparent;\r\n }\r\n\r\n .tab-btn:hover {\r\n background-color: var(--bg-hover);\r\n color: var(--text-primary);\r\n }\r\n\r\n .tab-btn.active {\r\n color: var(--primary-color);\r\n border-bottom-color: var(--primary-color);\r\n background-color: var(--bg-primary);\r\n }\r\n\r\n .notifications-list {\r\n flex: 1;\r\n overflow-y: auto;\r\n }\r\n\r\n .notification-item {\r\n display: flex;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--border-light);\r\n cursor: pointer;\r\n background-color: var(--bg-tertiary);\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .notification-item:hover {\r\n background-color: var(--bg-hover);\r\n }\r\n\r\n .notification-item.unread {\r\n background-color: var(--bg-unread);\r\n }\r\n\r\n .notification-content {\r\n flex: 1;\r\n min-width: 0;\r\n }\r\n\r\n .notification-title {\r\n font-weight: 600;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n .notification-message {\r\n color: var(--text-secondary);\r\n font-size: 12px;\r\n line-height: 1.4;\r\n margin-bottom: 6px;\r\n display: -webkit-box;\r\n -webkit-line-clamp: 2;\r\n -webkit-box-orient: vertical;\r\n overflow: hidden;\r\n }\r\n\r\n .notification-meta {\r\n display: flex;\r\n justify-content: space-between;\r\n font-size: 12px;\r\n color: var(--text-muted);\r\n }\r\n\r\n .app-name {\r\n font-weight: 500;\r\n color: var(--primary-color);\r\n }\r\n\r\n .read-btn {\r\n background: none;\r\n border: none;\r\n color: var(--text-muted);\r\n cursor: pointer;\r\n font-size: 14px;\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .read-btn:hover {\r\n color: var(--success-color);\r\n }\r\n\r\n .delete-btn {\r\n background: none;\r\n border: none;\r\n color: var(--text-muted);\r\n cursor: pointer;\r\n font-size: 14px;\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .delete-btn:hover {\r\n color: var(--error-color);\r\n }\r\n\r\n .empty-state {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: 100%;\r\n color: var(--text-muted);\r\n font-size: 14px;\r\n }\r\n\r\n .panel-footer {\r\n padding: 12px 16px;\r\n border-top: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n }\r\n\r\n .footer-actions {\r\n display: flex;\r\n gap: 8px;\r\n }\r\n\r\n .footer-actions .action-btn {\r\n flex: 1;\r\n }\r\n\r\n .action-btn {\r\n width: 100%;\r\n padding: 8px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .action-btn:hover {\r\n background-color: var(--primary-hover);\r\n }\r\n\r\n .delete-all-btn {\r\n background-color: var(--error-color);\r\n color: white;\r\n }\r\n\r\n .delete-all-btn:hover {\r\n background-color: #d32f2f; /* Darker red for hover */\r\n }\r\n\r\n /* Modal Overlay */\r\n .modal-overlay {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n bottom: 0;\r\n width: 100vw;\r\n height: 100vh;\r\n background-color: rgba(0, 0, 0, 0.5);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n z-index: 1060;\r\n }\r\n\r\n .modal-container {\r\n background: var(--bg-primary);\r\n border-radius: 8px;\r\n width: 90%;\r\n max-width: 600px;\r\n max-height: 80vh;\r\n display: flex;\r\n flex-direction: column;\r\n box-shadow: 0 4px 20px var(--shadow);\r\n }\r\n\r\n .modal-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n padding: 16px 20px;\r\n border-bottom: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n border-radius: 8px 8px 0 0;\r\n }\r\n\r\n .modal-header h3 {\r\n margin: 0;\r\n font-size: 18px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .modal-meta {\r\n display: flex;\r\n justify-content: space-between;\r\n padding: 8px 20px;\r\n font-size: 12px;\r\n color: var(--text-muted);\r\n background-color: var(--bg-tertiary);\r\n border-bottom: 1px solid var(--border-light);\r\n }\r\n\r\n .modal-body {\r\n padding: 20px;\r\n overflow-y: auto;\r\n flex: 1;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n line-height: 1.6;\r\n }\r\n\r\n .modal-footer {\r\n padding: 12px 20px;\r\n border-top: 1px solid var(--border-color);\r\n background-color: var(--bg-secondary);\r\n border-radius: 0 0 8px 8px;\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n\r\n .modal-footer .action-btn {\r\n width: auto;\r\n padding: 8px 24px;\r\n }\r\n\r\n @media (max-width: 600px) {\r\n .notification-panel {\r\n width: 100%;\r\n right: -100%;\r\n }\r\n\r\n .modal-container {\r\n width: 95%;\r\n max-height: 90vh;\r\n }\r\n }\r\n `]\r\n})\r\nexport class NotificationPanelComponent implements OnInit, OnDestroy {\r\n @Output() notificationRead = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n isOpen = false;\r\n notifications: NotificationDto[] = [];\r\n currentTheme: Theme = 'light';\r\n activeTab: 'unread' | 'read' = 'unread'; // Default to unread tab\r\n private destroy$ = new Subject<void>();\r\n\r\n get unreadNotifications(): NotificationDto[] {\r\n return this.notifications.filter(n => !n.isRead);\r\n }\r\n\r\n get readNotifications(): NotificationDto[] {\r\n return this.notifications.filter(n => n.isRead);\r\n }\r\n\r\n get currentNotifications(): NotificationDto[] {\r\n return this.activeTab === 'unread' ? this.unreadNotifications : this.readNotifications;\r\n }\r\n\r\n selectedNotification: NotificationDto | null = null;\r\n selectedNotificationHtml: SafeHtml | null = null;\r\n selectedNotificationDate: string = '';\r\n\r\n // Returns plain text message for list display\r\n getNotificationMessage(notification: NotificationDto): string {\r\n return notification.message || '';\r\n }\r\n\r\n private readonly sanitizer = inject(DomSanitizer);\r\n\r\n constructor(private authService: MesAuthService, private toastService: ToastService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n this.loadNotifications();\r\n\r\n // Listen for new real-time notifications\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe((notification: RealTimeNotificationDto) => {\r\n // Show toast for new notification\r\n this.toastService.show(\r\n notification.messageHtml || notification.message || '',\r\n notification.title,\r\n 'info',\r\n 5000\r\n );\r\n // Reload notifications list\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n private loadNotifications() {\r\n this.authService.getNotifications(1, 50, true).subscribe({ // includeRead = true to get both read and unread\r\n next: (response: PagedList<NotificationDto>) => {\r\n this.notifications = response.items || [];\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n open() {\r\n this.isOpen = true;\r\n this.activeTab = 'unread'; // Reset to unread tab when opening\r\n }\r\n\r\n close() {\r\n this.isOpen = false;\r\n }\r\n\r\n switchTab(tab: 'unread' | 'read') {\r\n this.activeTab = tab;\r\n }\r\n\r\n openDetails(notification: NotificationDto) {\r\n this.selectedNotification = notification;\r\n // Cache computed values to avoid re-rendering on every change detection cycle\r\n const html = notification.messageHtml || notification.message || '';\r\n this.selectedNotificationHtml = this.sanitizer.bypassSecurityTrustHtml(html);\r\n this.selectedNotificationDate = this.formatDate(notification.createdAt);\r\n // Mark as read when opening details (if not already read)\r\n if (!notification.isRead) {\r\n this.authService.markAsRead(notification.id).subscribe({\r\n next: () => {\r\n notification.isRead = true;\r\n this.notificationRead.emit();\r\n },\r\n error: () => {}\r\n });\r\n }\r\n }\r\n\r\n closeDetails() {\r\n this.selectedNotification = null;\r\n this.selectedNotificationHtml = null;\r\n this.selectedNotificationDate = '';\r\n }\r\n\r\n markAsRead(notificationId: string, event?: Event) {\r\n if (event) {\r\n event.stopPropagation();\r\n }\r\n this.authService.markAsRead(notificationId).subscribe({\r\n next: () => {\r\n const notification = this.notifications.find(n => n.id === notificationId);\r\n if (notification) {\r\n notification.isRead = true;\r\n this.notificationRead.emit();\r\n }\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n markAllAsRead() {\r\n this.authService.markAllAsRead().subscribe({\r\n next: () => {\r\n this.notifications.forEach(n => n.isRead = true);\r\n this.notificationRead.emit();\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n deleteAllRead() {\r\n const readNotificationIds = this.notifications\r\n .filter(n => n.isRead)\r\n .map(n => n.id);\r\n\r\n // Delete all read notifications\r\n const deletePromises = readNotificationIds.map(id =>\r\n this.authService.deleteNotification(id).toPromise()\r\n );\r\n\r\n Promise.all(deletePromises).then(() => {\r\n // Remove all read notifications from the local array\r\n this.notifications = this.notifications.filter(n => !n.isRead);\r\n }).catch((err) => {\r\n // If bulk delete fails, reload notifications to get current state\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n deleteAllUnread() {\r\n const unreadNotificationIds = this.notifications\r\n .filter(n => !n.isRead)\r\n .map(n => n.id);\r\n\r\n // Delete all unread notifications\r\n const deletePromises = unreadNotificationIds.map(id =>\r\n this.authService.deleteNotification(id).toPromise()\r\n );\r\n\r\n Promise.all(deletePromises).then(() => {\r\n // Remove all unread notifications from the local array\r\n this.notifications = this.notifications.filter(n => n.isRead);\r\n }).catch((err) => {\r\n // If bulk delete fails, reload notifications to get current state\r\n this.loadNotifications();\r\n });\r\n }\r\n\r\n delete(notificationId: string, event: Event) {\r\n event.stopPropagation();\r\n this.authService.deleteNotification(notificationId).subscribe({\r\n next: () => {\r\n this.notifications = this.notifications.filter(n => n.id !== notificationId);\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n formatDate(dateString: string): string {\r\n // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)\r\n const normalizedDateString = this.parseUtcDate(dateString);\r\n const date = new Date(normalizedDateString);\r\n \r\n // Check if the date is valid\r\n if (isNaN(date.getTime())) {\r\n return 'Invalid date';\r\n }\r\n \r\n const now = new Date();\r\n const diffMs = now.getTime() - date.getTime();\r\n const diffMins = Math.floor(diffMs / 60000);\r\n const diffHours = Math.floor(diffMs / 3600000);\r\n const diffDays = Math.floor(diffMs / 86400000);\r\n\r\n if (diffMins < 1) return 'Now';\r\n if (diffMins < 60) return `${diffMins}m ago`;\r\n if (diffHours < 24) return `${diffHours}h ago`;\r\n if (diffDays < 7) return `${diffDays}d ago`;\r\n \r\n return date.toLocaleDateString();\r\n }\r\n\r\n // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)\r\n private parseUtcDate(dateStr: string): string {\r\n // Handle date strings that might be missing the 'T' separator\r\n // Convert formats like \"2023-12-01 12:30:45\" to \"2023-12-01T12:30:45\"\r\n let normalized = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T');\r\n \r\n // If no timezone indicator, assume UTC by appending 'Z'\r\n if (!normalized.endsWith('Z') && !normalized.includes('+') && !normalized.includes('-', 10)) {\r\n normalized += 'Z';\r\n }\r\n \r\n return normalized;\r\n }\r\n}\r\n","import { Component, ViewChild, AfterViewInit } from '@angular/core';\r\nimport { ToastContainerComponent } from './toast-container.component';\r\nimport { UserProfileComponent } from './user-profile.component';\r\nimport { NotificationPanelComponent } from './notification-panel.component';\r\n\r\n@Component({\r\n selector: 'ma-user',\r\n standalone: true,\r\n imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent],\r\n template: `\r\n <ma-toast-container></ma-toast-container>\r\n <div class=\"user-header\">\r\n <ma-user-profile (notificationClick)=\"notificationPanel.open()\"></ma-user-profile>\r\n </div>\r\n <ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\r\n `,\r\n styles: [`\r\n .user-header {\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n `]\r\n})\r\nexport class MaUserComponent implements AfterViewInit {\r\n @ViewChild(UserProfileComponent) userProfile?: UserProfileComponent;\r\n\r\n ngAfterViewInit() {\r\n // Ensure proper initialization\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n\r\n onNotificationRead() {\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { MesAuthService } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-notification-badge',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n `,\r\n styles: [`\r\n :host {\r\n --error-color: #f44336;\r\n }\r\n\r\n :host(.theme-dark) {\r\n --error-color: #ef5350;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n `]\r\n})\r\nexport class NotificationBadgeComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n \r\n unreadCount = 0;\r\n currentTheme: Theme = 'light';\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private authService: MesAuthService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.hasUser = !!user;\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n this.loadUnreadCount();\r\n });\r\n \r\n // Listen for new notifications\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n private loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => console.error('Error loading unread count:', err)\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.MesAuthService","i3.ThemeService","i1.ToastService","i2.ThemeService","i2.ToastService"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mesauth-angular.mjs","sources":["../../src/mes-auth.service.ts","../../src/mes-auth.interceptor.ts","../../src/mes-auth.module.ts","../../src/theme.service.ts","../../src/user-profile.component.ts","../../src/toast.service.ts","../../src/toast-container.component.ts","../../src/notification-panel.component.ts","../../src/ma-user.component.ts","../../src/notification-badge.component.ts","../../src/mesauth-angular.ts"],"sourcesContent":["import { inject, Injectable, InjectionToken, EnvironmentProviders, makeEnvironmentProviders, provideAppInitializer, NgZone } from '@angular/core';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\r\nimport { BehaviorSubject, Subject, Observable, of, EMPTY } from 'rxjs';\r\nimport { tap, catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\n\r\nexport interface MesAuthConfig {\r\n apiBaseUrl: string;\r\n withCredentials?: boolean;\r\n userBaseUrl?: string;\r\n}\r\n\r\n/** Injection token for MesAuth configuration */\r\nexport const MES_AUTH_CONFIG = new InjectionToken<MesAuthConfig>('MES_AUTH_CONFIG');\r\n\r\n/**\r\n * Provides MesAuth with configuration.\r\n * This is the recommended way to set up mesauth-angular in standalone apps.\r\n *\r\n * @example\r\n * ```typescript\r\n * // app.config.ts\r\n * export const appConfig: ApplicationConfig = {\r\n * providers: [\r\n * provideHttpClient(withInterceptors([mesAuthInterceptor])),\r\n * provideMesAuth({\r\n * apiBaseUrl: 'https://auth.example.com',\r\n * userBaseUrl: 'https://app.example.com'\r\n * })\r\n * ]\r\n * };\r\n * ```\r\n */\r\nexport function provideMesAuth(config: MesAuthConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n { provide: MES_AUTH_CONFIG, useValue: config },\r\n MesAuthService,\r\n provideAppInitializer(() => {\r\n const mesAuthService = inject(MesAuthService);\r\n const httpClient = inject(HttpClient);\r\n const router = inject(Router);\r\n const ngZone = inject(NgZone);\r\n mesAuthService.init(config, httpClient, router, ngZone);\r\n })\r\n ]);\r\n}\r\n\r\nexport interface IUser {\r\n userId?: string;\r\n userName?: string;\r\n fullName?: string;\r\n gender?: string;\r\n email?: string;\r\n phoneNumber?: string;\r\n department?: string;\r\n position?: string;\r\n tokenVersion?: string;\r\n permEndpoint?: string;\r\n perms?: Set<string>;\r\n employeeCode?: string;\r\n avatarPath?: string;\r\n loginMethod?: number;\r\n hrFullNameVn?: string;\r\n hrFullNameEn?: string;\r\n hrPosition?: string;\r\n hrJobTitle?: string;\r\n hrGender?: string;\r\n hrMobile?: string;\r\n hrEmail?: string;\r\n hrJoinDate?: string;\r\n hrBirthDate?: string;\r\n hrWorkStatus?: string;\r\n hrDoiTuong?: string;\r\n hrTeamCode?: string;\r\n hrLineCode?: string;\r\n}\r\n\r\nexport enum NotificationType {\r\n Info = 'Info',\r\n Warning = 'Warning',\r\n Error = 'Error',\r\n Success = 'Success'\r\n}\r\n\r\nexport interface NotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n isRead: boolean;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\nexport interface FrontEndRoute {\r\n id: number;\r\n roleId: string;\r\n roleName: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n children: FrontEndRoute[];\r\n}\r\n\r\nexport interface UserFrontEndRoutesGrouped {\r\n appId: string;\r\n appName: string;\r\n feUrl?: string;\r\n routes: FrontEndRoute[];\r\n}\r\n\r\nexport interface FrontEndRouteMaster {\r\n id: number;\r\n appId: string;\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder: number;\r\n isLabel: boolean;\r\n isActive: boolean;\r\n createdAt: string;\r\n updatedAt?: string;\r\n}\r\n\r\nexport interface CreateFrontEndRouteDto {\r\n routePath: string;\r\n routeName: string;\r\n description?: string;\r\n icon?: string;\r\n cssClass?: string;\r\n parentId?: number | null;\r\n sortOrder?: number;\r\n isLabel?: boolean;\r\n}\r\n\r\nexport interface PagedList<T> {\r\n items: T[];\r\n totalCount: number;\r\n page: number;\r\n pageSize: number;\r\n totalPages: number;\r\n hasNext: boolean;\r\n hasPrevious: boolean;\r\n}\r\n\r\nexport interface RealTimeNotificationDto {\r\n id: string;\r\n title: string;\r\n message: string;\r\n messageHtml?: string;\r\n url?: string;\r\n type: NotificationType;\r\n createdAt: string;\r\n sourceAppName: string;\r\n sourceAppIconUrl?: string;\r\n}\r\n\r\n@Injectable()\r\nexport class MesAuthService {\r\n private hubConnection: HubConnection | null = null;\r\n private _currentUser = new BehaviorSubject<IUser | null>(null);\r\n public currentUser$: Observable<IUser | null> = this._currentUser.asObservable();\r\n private _notifications = new Subject<any>();\r\n public notifications$: Observable<any> = this._notifications.asObservable();\r\n\r\n private apiBase = '';\r\n private config: MesAuthConfig | null = null;\r\n private http!: HttpClient;\r\n private router?: Router;\r\n private ngZone: NgZone | null = null;\r\n\r\n constructor() {\r\n // Empty constructor - all dependencies passed to init()\r\n }\r\n\r\n init(config: MesAuthConfig, httpClient: HttpClient, router?: Router, ngZone?: NgZone) {\r\n this.config = config;\r\n this.http = httpClient;\r\n this.router = router;\r\n this.ngZone = ngZone ?? null;\r\n this.apiBase = config.apiBaseUrl.replace(/\\/$/, '');\r\n\r\n // Fetch user once on init. Route changes do NOT re-fetch the user.\r\n // Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.\r\n // SignalR handles real-time notification delivery without polling.\r\n this.fetchCurrentUser().subscribe();\r\n }\r\n\r\n getConfig(): MesAuthConfig | null {\r\n return this.config;\r\n }\r\n\r\n private fetchCurrentUser(): Observable<any> {\r\n if (!this.apiBase) return EMPTY;\r\n const url = `${this.apiBase}/auth/me`;\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap((u) => {\r\n this._currentUser.next(u);\r\n if (u && this.config) {\r\n this.startConnection(this.config);\r\n }\r\n }),\r\n catchError((err) => {\r\n // Silently handle auth errors (401/403) - user is not logged in\r\n if (err.status === 401 || err.status === 403) {\r\n this._currentUser.next(null);\r\n }\r\n return of(null);\r\n })\r\n );\r\n }\r\n\r\n public getUnreadCount(): Observable<any> {\r\n return this.http.get(`${this.apiBase}/notif/me/unread-count`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public getNotifications(page: number = 1, pageSize: number = 20, includeRead: boolean = false, type?: string): Observable<any> {\r\n let url = `${this.apiBase}/notif/me?page=${page}&pageSize=${pageSize}&includeRead=${includeRead}`;\r\n if (type) {\r\n url += `&type=${type}`;\r\n }\r\n return this.http.get(url, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAsRead(notificationId: string): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/${notificationId}/read`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public markAllAsRead(): Observable<any> {\r\n return this.http.patch(`${this.apiBase}/notif/me/read-all`, {}, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n public deleteNotification(notificationId: string): Observable<any> {\r\n return this.http.delete(`${this.apiBase}/notif/${notificationId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get frontend routes assigned to the current user\r\n * Returns routes grouped by application\r\n */\r\n public getFrontEndRoutes(): Observable<UserFrontEndRoutesGrouped[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<UserFrontEndRoutesGrouped[]>(`${this.apiBase}/fe-routes/me`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Get master routes for a specific application\r\n * @param appId - The application ID\r\n */\r\n public getRouteMasters(appId: string): Observable<FrontEndRouteMaster[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<FrontEndRouteMaster[]>(`${this.apiBase}/fe-routes/masters/${appId}`, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Register/sync frontend routes for an application\r\n * This is typically called on app startup to sync routes from the frontend app\r\n * @param appId - The application ID (passed via X-App-Id header)\r\n * @param routes - Array of route definitions\r\n */\r\n public registerFrontEndRoutes(appId: string, routes: CreateFrontEndRouteDto[]): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n const headers = { 'X-App-Id': appId };\r\n return this.http.post(`${this.apiBase}/fe-routes/register`, routes, {\r\n headers,\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Create a new route master\r\n * @param appId - The application ID\r\n * @param route - Route details\r\n */\r\n public createRouteMaster(appId: string, route: CreateFrontEndRouteDto): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters`, {\r\n appId,\r\n ...route\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Update an existing route master\r\n * @param routeId - The route master ID\r\n * @param route - Updated route details\r\n */\r\n public updateRouteMaster(routeId: number, route: Partial<CreateFrontEndRouteDto> & { isActive?: boolean }): Observable<FrontEndRouteMaster> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.put<FrontEndRouteMaster>(`${this.apiBase}/fe-routes/masters/${routeId}`, route, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Delete a route master\r\n * @param routeId - The route master ID\r\n */\r\n public deleteRouteMaster(routeId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/masters/${routeId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Assign a route to a role\r\n * @param routeMasterId - The route master ID\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public assignRouteToRole(routeMasterId: number, roleId: string): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.post(`${this.apiBase}/fe-routes/mappings`, {\r\n routeMasterId,\r\n roleId\r\n }, { withCredentials: this.config?.withCredentials ?? true });\r\n }\r\n\r\n /**\r\n * Remove a route assignment from a role\r\n * @param mappingId - The mapping ID\r\n */\r\n public removeRouteFromRole(mappingId: number): Observable<any> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.delete(`${this.apiBase}/fe-routes/mappings/${mappingId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n /**\r\n * Get route-to-role mappings for a specific role\r\n * @param roleId - The role ID (GUID)\r\n */\r\n public getRouteMappingsByRole(roleId: string): Observable<any[]> {\r\n if (!this.apiBase) throw new Error('MesAuth not initialized');\r\n return this.http.get<any[]>(`${this.apiBase}/fe-routes/mappings?roleId=${roleId}`, {\r\n withCredentials: this.config?.withCredentials ?? true\r\n });\r\n }\r\n\r\n private startConnection(config: MesAuthConfig) {\r\n if (this.hubConnection) return;\r\n const signalrUrl = config.apiBaseUrl.replace(/\\/$/, '') + '/hub/notification';\r\n const builder = new HubConnectionBuilder()\r\n .withUrl(signalrUrl, { withCredentials: config.withCredentials ?? true })\r\n .withAutomaticReconnect()\r\n .configureLogging(LogLevel.Warning);\r\n\r\n this.hubConnection = builder.build();\r\n\r\n this.hubConnection.on('ReceiveNotification', (n: any) => {\r\n if (this.ngZone) {\r\n this.ngZone.run(() => this._notifications.next(n));\r\n } else {\r\n this._notifications.next(n);\r\n }\r\n });\r\n\r\n this.hubConnection.start().then(() => {}).catch((err) => {});\r\n\r\n this.hubConnection.onclose(() => {});\r\n this.hubConnection.onreconnecting(() => {});\r\n this.hubConnection.onreconnected(() => {});\r\n }\r\n\r\n public stop() {\r\n if (!this.hubConnection) return;\r\n this.hubConnection.stop().catch(() => {});\r\n this.hubConnection = null;\r\n }\r\n\r\n public logout(): Observable<any> {\r\n const url = `${this.apiBase}/auth/logout`;\r\n return this.http.post(url, {}, { withCredentials: this.config?.withCredentials ?? true }).pipe(\r\n tap(() => {\r\n this._currentUser.next(null);\r\n this.stop();\r\n })\r\n );\r\n }\r\n\r\n public get currentUser(): IUser | null {\r\n return this._currentUser.value;\r\n }\r\n\r\n public get isAuthenticated(): boolean {\r\n return this._currentUser.value !== null;\r\n }\r\n\r\n /**\r\n * Refreshes the current user from the server.\r\n * Returns an Observable that completes when the user data is loaded.\r\n * Callers can subscribe to wait for completion before proceeding (e.g., navigating after login).\r\n */\r\n public refreshUser(): Observable<any> {\r\n return this.fetchCurrentUser();\r\n }\r\n}\r\n","import { inject } from '@angular/core';\r\nimport { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';\r\nimport { throwError } from 'rxjs';\r\nimport { catchError } from 'rxjs/operators';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n// Track if we're currently redirecting to prevent loopback\r\nlet isRedirecting = false;\r\n\r\n/**\r\n * Functional HTTP interceptor for handling 401/403 auth errors.\r\n * Redirects to login page on 401, and to 403 page on 403.\r\n * Includes loopback prevention to avoid infinite redirects.\r\n */\r\nexport const mesAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const authService = inject(MesAuthService);\r\n const router = inject(Router);\r\n\r\n return next(req).pipe(\r\n catchError((error: HttpErrorResponse) => {\r\n const status = error.status;\r\n\r\n // Check if we should handle this error and prevent loopback\r\n if ((status === 401 || status === 403) && !isRedirecting) {\r\n const config = authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n\r\n const currentUrl = router.url + (window.location.hash || '');\r\n const returnUrl = encodeURIComponent(currentUrl);\r\n\r\n // Avoid loops if already on auth/unauth pages\r\n const isLoginPage = currentUrl.includes('/login');\r\n const is403Page = currentUrl.includes('/403');\r\n const isAuthPage = currentUrl.includes('/auth');\r\n // Public pages that should never trigger a 401 redirect (e.g., register, password reset)\r\n const isPublicPage = currentUrl.includes('/register')\r\n || currentUrl.includes('/forgot-password')\r\n || currentUrl.includes('/reset-password');\r\n // Skip redirect for the initial /auth/me check (app startup when not logged in)\r\n const isMeAuthPage = req.url.includes('/auth/me');\r\n\r\n if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage && !isPublicPage) {\r\n // Session expired or not authenticated - redirect to login\r\n // No isAuthenticated check: when session expires, BehaviorSubject still holds\r\n // stale user data, so checking isAuthenticated would block the redirect.\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n } else if (status === 403 && !is403Page) {\r\n isRedirecting = true;\r\n setTimeout(() => { isRedirecting = false; }, 5000);\r\n let redirectUrl = `${baseUrl}/403?returnUrl=${returnUrl}`;\r\n if (error.error && error.error.required) {\r\n redirectUrl += `&required=${encodeURIComponent(error.error.required)}`;\r\n }\r\n window.location.href = redirectUrl;\r\n }\r\n }\r\n return throwError(() => error);\r\n })\r\n );\r\n};\r\n","import { NgModule } from '@angular/core';\r\nimport { MesAuthService } from './mes-auth.service';\r\n\r\n@NgModule({\r\n providers: [\r\n MesAuthService\r\n ]\r\n})\r\nexport class MesAuthModule {}\r\n","import { Injectable, OnDestroy } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport type Theme = 'light' | 'dark';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class ThemeService implements OnDestroy {\r\n private _currentTheme = new BehaviorSubject<Theme>('light');\r\n public currentTheme$: Observable<Theme> = this._currentTheme.asObservable();\r\n private observer: MutationObserver | null = null;\r\n\r\n constructor() {\r\n this.detectTheme();\r\n this.startWatching();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.stopWatching();\r\n }\r\n\r\n private detectTheme(): void {\r\n const html = document.documentElement;\r\n const isDark = html.classList.contains('dark') ||\r\n html.getAttribute('data-theme') === 'dark' ||\r\n html.getAttribute('theme') === 'dark' ||\r\n html.getAttribute('data-coreui-theme') === 'dark';\r\n\r\n this._currentTheme.next(isDark ? 'dark' : 'light');\r\n }\r\n\r\n private startWatching(): void {\r\n if (typeof MutationObserver === 'undefined') {\r\n // Fallback for older browsers - check periodically\r\n setInterval(() => this.detectTheme(), 1000);\r\n return;\r\n }\r\n\r\n this.observer = new MutationObserver(() => {\r\n this.detectTheme();\r\n });\r\n\r\n this.observer.observe(document.documentElement, {\r\n attributes: true,\r\n attributeFilter: ['class', 'data-theme', 'theme', 'data-coreui-theme']\r\n });\r\n }\r\n\r\n private stopWatching(): void {\r\n if (this.observer) {\r\n this.observer.disconnect();\r\n this.observer = null;\r\n }\r\n }\r\n\r\n get currentTheme(): Theme {\r\n return this._currentTheme.value;\r\n }\r\n\r\n // Method to manually set theme if needed\r\n setTheme(theme: Theme): void {\r\n this._currentTheme.next(theme);\r\n }\r\n\r\n // Re-detect theme from DOM\r\n refreshTheme(): void {\r\n this.detectTheme();\r\n }\r\n}","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding, HostListener, signal, ChangeDetectorRef } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { Router } from '@angular/router';\r\nimport { MesAuthService, IUser } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-user-profile',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <div class=\"user-profile-container\">\r\n <!-- Not logged in -->\r\n <ng-container *ngIf=\"!currentUser()\">\r\n <button class=\"login-btn\" (click)=\"onLogin()\">\r\n Login\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Logged in -->\r\n <ng-container *ngIf=\"currentUser()\">\r\n <div class=\"user-header\">\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n\r\n <div class=\"user-menu-wrapper\">\r\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\">\r\n <img \r\n *ngIf=\"currentUser().fullName || currentUser().userName\"\r\n [src]=\"getAvatarUrl(currentUser())\" \r\n [alt]=\"currentUser().fullName || currentUser().userName\"\r\n class=\"avatar\"\r\n />\r\n <span *ngIf=\"!(currentUser().fullName || currentUser().userName)\" class=\"avatar-initial\">\r\n {{ getLastNameInitial(currentUser()) }}\r\n </span>\r\n </button>\r\n\r\n <div class=\"mes-dropdown-menu\" *ngIf=\"dropdownOpen\">\r\n <div class=\"mes-dropdown-header\">\r\n {{ currentUser().fullName || currentUser().userName }}\r\n </div>\r\n <button class=\"mes-dropdown-item profile-link\" (click)=\"onViewProfile()\">\r\n View Profile\r\n </button>\r\n <button class=\"mes-dropdown-item logout-item\" (click)=\"onLogout()\">\r\n Logout\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --primary-color: #1976d2;\r\n --primary-hover: #1565c0;\r\n --primary-light: rgba(25, 118, 210, 0.1);\r\n --error-color: #f44336;\r\n --error-light: #ffebee;\r\n --text-primary: #333;\r\n --text-secondary: #666;\r\n --text-muted: #999;\r\n --bg-primary: white;\r\n --bg-secondary: #f5f5f5;\r\n --bg-tertiary: #fafafa;\r\n --bg-hover: #f5f5f5;\r\n --border-color: #e0e0e0;\r\n --border-light: #f0f0f0;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --shadow-light: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --primary-color: #90caf9;\r\n --primary-hover: #64b5f6;\r\n --primary-light: rgba(144, 202, 249, 0.1);\r\n --error-color: #ef5350;\r\n --error-light: rgba(239, 83, 80, 0.1);\r\n --text-primary: #e0e0e0;\r\n --text-secondary: #b0b0b0;\r\n --text-muted: #888;\r\n --bg-primary: #1e1e1e;\r\n --bg-secondary: #2d2d2d;\r\n --bg-tertiary: #252525;\r\n --bg-hover: #333;\r\n --border-color: #404040;\r\n --border-light: #333;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --shadow-light: rgba(0, 0, 0, 0.2);\r\n }\r\n\r\n .user-profile-container {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n padding: 0 16px;\r\n }\r\n\r\n .login-btn {\r\n padding: 8px 16px;\r\n background-color: var(--primary-color);\r\n color: white;\r\n border: none;\r\n border-radius: 4px;\r\n cursor: pointer;\r\n font-weight: 500;\r\n transition: background-color 0.3s;\r\n }\r\n\r\n .login-btn:hover {\r\n background-color: var(--primary-hover);\r\n }\r\n\r\n .user-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 16px;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n\r\n .user-menu-wrapper {\r\n position: relative;\r\n }\r\n\r\n .user-menu-btn {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n padding: 4px;\r\n border-radius: 50%;\r\n transition: background-color 0.2s;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n }\r\n\r\n .user-menu-btn:hover {\r\n background-color: var(--primary-light);\r\n }\r\n\r\n .avatar {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n object-fit: cover;\r\n background-color: #e0e0e0;\r\n }\r\n\r\n .avatar-initial {\r\n width: 40px;\r\n height: 40px;\r\n border-radius: 50%;\r\n background-color: var(--primary-color);\r\n color: white;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-weight: bold;\r\n font-size: 16px;\r\n }\r\n\r\n .mes-dropdown-menu {\r\n position: absolute;\r\n top: calc(100% + 8px);\r\n right: 0;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n border-radius: 4px;\r\n box-shadow: 0 2px 8px var(--shadow);\r\n min-width: 200px;\r\n z-index: 1000;\r\n overflow: hidden;\r\n }\r\n\r\n .mes-dropdown-header {\r\n padding: 12px 16px;\r\n border-bottom: 1px solid var(--border-light);\r\n font-weight: 600;\r\n color: var(--text-primary);\r\n font-size: 14px;\r\n }\r\n\r\n .mes-dropdown-item {\r\n display: block;\r\n width: 100%;\r\n padding: 12px 16px;\r\n border: none;\r\n background: none;\r\n text-align: left;\r\n cursor: pointer;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n text-decoration: none;\r\n transition: background-color 0.2s;\r\n }\r\n\r\n .mes-dropdown-item:hover {\r\n background-color: var(--bg-hover);\r\n }\r\n\r\n .profile-link {\r\n color: var(--primary-color);\r\n }\r\n\r\n .logout-item {\r\n border-top: 1px solid var(--border-light);\r\n color: var(--error-color);\r\n }\r\n\r\n .logout-item:hover {\r\n background-color: var(--error-light);\r\n }\r\n\r\n .user-info {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 2px;\r\n }\r\n\r\n .user-name {\r\n font-weight: 500;\r\n font-size: 14px;\r\n color: var(--text-primary);\r\n }\r\n\r\n .user-position {\r\n font-size: 12px;\r\n color: var(--text-secondary);\r\n }\r\n\r\n .logout-btn {\r\n background: none;\r\n border: none;\r\n font-size: 20px;\r\n cursor: pointer;\r\n color: var(--text-secondary);\r\n padding: 4px 8px;\r\n transition: color 0.2s;\r\n }\r\n\r\n .logout-btn:hover {\r\n color: var(--primary-color);\r\n }\r\n\r\n @media (max-width: 768px) {\r\n .user-info {\r\n display: none;\r\n }\r\n\r\n .avatar {\r\n width: 32px;\r\n height: 32px;\r\n }\r\n }\r\n `]\r\n})\r\nexport class UserProfileComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n currentUser = signal<IUser | null>(null);\r\n currentTheme: Theme = 'light';\r\n unreadCount = 0;\r\n dropdownOpen = false;\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n // Signal to force avatar refresh\r\n avatarRefresh = signal<number>(Date.now());\r\n\r\n constructor(private authService: MesAuthService, private router: Router, private themeService: ThemeService, private cdr: ChangeDetectorRef) {}\r\n\r\n ngOnInit() {\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.currentUser.set(user);\r\n this.hasUser = !!user;\r\n // Force avatar refresh when user changes\r\n this.avatarRefresh.set(Date.now());\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n } else {\r\n this.loadUnreadCount();\r\n }\r\n this.cdr.markForCheck();\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n // Listen for new real-time notifications (SignalR only)\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => {}\r\n });\r\n }\r\n\r\n getAvatarUrl(user: IUser): string {\r\n // Use the refresh signal to force update\r\n const refresh = this.avatarRefresh();\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.apiBaseUrl || '';\r\n \r\n // If user has avatarPath, use it directly\r\n if (user.avatarPath) {\r\n // If avatarPath is already a full URL, use it as-is\r\n if (user.avatarPath.startsWith('http://') || user.avatarPath.startsWith('https://')) {\r\n return user.avatarPath;\r\n }\r\n // If it's a relative path, construct full URL with refresh timestamp\r\n return `${baseUrl.replace(/\\/$/, '')}${user.avatarPath}?t=${refresh}`;\r\n }\r\n\r\n // Fallback: construct URL using userId\r\n const userId = user.userId;\r\n if (userId && baseUrl) {\r\n return `${baseUrl.replace(/\\/$/, '')}/auth/${userId}/avatar?t=${refresh}`;\r\n }\r\n \r\n // Fallback to UI avatars service if no userId or baseUrl\r\n const displayName = user.userName || user.userId || 'User';\r\n return `https://ui-avatars.com/api/?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;\r\n }\r\n\r\n getLastNameInitial(user: IUser): string {\r\n const fullName = user.fullName || user.userName || 'U';\r\n const parts = fullName.split(' ');\r\n const lastPart = parts[parts.length - 1];\r\n return lastPart.charAt(0).toUpperCase();\r\n }\r\n\r\n toggleDropdown() {\r\n this.dropdownOpen = !this.dropdownOpen;\r\n }\r\n\r\n @HostListener('document:click', ['$event'])\r\n onDocumentClick(event: Event) {\r\n const target = event.target as HTMLElement;\r\n const clickedInside = target.closest('.user-menu-wrapper');\r\n if (!clickedInside) {\r\n this.dropdownOpen = false;\r\n }\r\n }\r\n\r\n onLogin() {\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(this.router.url);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n }\r\n\r\n onViewProfile() {\r\n this.router.navigate(['/profile']);\r\n this.dropdownOpen = false;\r\n }\r\n\r\n onLogout() {\r\n this.authService.logout().subscribe({\r\n next: () => {\r\n // Clear current user after successful logout\r\n this.dropdownOpen = false;\r\n \r\n // Navigate to login with return URL\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n const returnUrl = encodeURIComponent(window.location.href);\r\n window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;\r\n },\r\n error: (err) => {\r\n // Still navigate to login even if logout fails\r\n const config = this.authService.getConfig();\r\n const baseUrl = config?.userBaseUrl || '';\r\n window.location.href = `${baseUrl}/login`;\r\n }\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n\r\n","import { Injectable } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\n\r\nexport interface Toast {\r\n id: string;\r\n message: string;\r\n title?: string;\r\n type: 'info' | 'success' | 'warning' | 'error';\r\n duration?: number;\r\n}\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class ToastService {\r\n private toasts$ = new BehaviorSubject<Toast[]>([]);\r\n public toasts: Observable<Toast[]> = this.toasts$.asObservable();\r\n\r\n show(message: string, title?: string, type: 'info' | 'success' | 'warning' | 'error' = 'info', duration: number = 5000) {\r\n const id = Math.random().toString(36).substr(2, 9);\r\n const toast: Toast = {\r\n id,\r\n message,\r\n title,\r\n type,\r\n duration\r\n };\r\n\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next([...currentToasts, toast]);\r\n\r\n if (duration > 0) {\r\n setTimeout(() => {\r\n this.remove(id);\r\n }, duration);\r\n }\r\n\r\n return id;\r\n }\r\n\r\n remove(id: string) {\r\n const currentToasts = this.toasts$.value;\r\n this.toasts$.next(currentToasts.filter(t => t.id !== id));\r\n }\r\n\r\n clear() {\r\n this.toasts$.next([]);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { ToastService, Toast } from './toast.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-toast-container',\r\n standalone: true,\r\n imports: [CommonModule],\r\n template: `\r\n <div class=\"toast-container\">\r\n <div \r\n *ngFor=\"let toast of toasts\"\r\n class=\"toast\"\r\n [class]=\"'toast-' + toast.type\"\r\n [@slideIn]\r\n >\r\n <div class=\"toast-content\">\r\n <div *ngIf=\"toast.title\" class=\"toast-title\">{{ toast.title }}</div>\r\n <div class=\"toast-message\" [innerHTML]=\"toast.message\"></div>\r\n </div>\r\n <button class=\"toast-close\" (click)=\"close(toast.id)\" aria-label=\"Close\">\r\n ✕\r\n </button>\r\n </div>\r\n </div>\r\n `,\r\n styles: [`\r\n :host {\r\n --info-color: #2196f3;\r\n --success-color: #4caf50;\r\n --warning-color: #ff9800;\r\n --error-color: #f44336;\r\n --text-primary: #333;\r\n --bg-primary: white;\r\n --shadow: rgba(0, 0, 0, 0.15);\r\n --text-secondary: #999;\r\n --border-color: rgba(0, 0, 0, 0.1);\r\n }\r\n\r\n :host(.theme-dark) {\r\n --info-color: #64b5f6;\r\n --success-color: #81c784;\r\n --warning-color: #ffb74d;\r\n --error-color: #ef5350;\r\n --text-primary: #e0e0e0;\r\n --bg-primary: #1e1e1e;\r\n --shadow: rgba(0, 0, 0, 0.3);\r\n --text-secondary: #888;\r\n --border-color: rgba(255, 255, 255, 0.1);\r\n }\r\n\r\n .toast-container {\r\n position: fixed;\r\n top: 20px;\r\n right: 20px;\r\n z-index: 9999;\r\n pointer-events: none;\r\n }\r\n\r\n .toast {\r\n display: flex;\r\n align-items: flex-start;\r\n gap: 12px;\r\n padding: 12px 16px;\r\n margin-bottom: 12px;\r\n border-radius: 4px;\r\n background: var(--bg-primary);\r\n border: 1px solid var(--border-color);\r\n box-shadow: 0 4px 12px var(--shadow);\r\n pointer-events: auto;\r\n min-width: 280px;\r\n max-width: 400px;\r\n animation: slideIn 0.3s ease-out;\r\n }\r\n\r\n .toast-content {\r\n flex: 1;\r\n }\r\n\r\n .toast-title {\r\n font-weight: 600;\r\n font-size: 14px;\r\n margin-bottom: 4px;\r\n }\r\n\r\n .toast-message {\r\n font-size: 13px;\r\n line-height: 1.4;\r\n }\r\n\r\n .toast-close {\r\n background: none;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 18px;\r\n color: var(--text-secondary);\r\n padding: 0;\r\n width: 24px;\r\n height: 24px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n flex-shrink: 0;\r\n transition: color 0.2s;\r\n }\r\n\r\n .toast-close:hover {\r\n color: var(--text-primary);\r\n }\r\n\r\n /* Toast types */\r\n .toast-info {\r\n border-left: 4px solid var(--info-color);\r\n }\r\n\r\n .toast-info .toast-title {\r\n color: var(--info-color);\r\n }\r\n\r\n .toast-info .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-success {\r\n border-left: 4px solid var(--success-color);\r\n }\r\n\r\n .toast-success .toast-title {\r\n color: var(--success-color);\r\n }\r\n\r\n .toast-success .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-warning {\r\n border-left: 4px solid var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-title {\r\n color: var(--warning-color);\r\n }\r\n\r\n .toast-warning .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n .toast-error {\r\n border-left: 4px solid var(--error-color);\r\n }\r\n\r\n .toast-error .toast-title {\r\n color: var(--error-color);\r\n }\r\n\r\n .toast-error .toast-message {\r\n color: var(--text-primary);\r\n }\r\n\r\n @keyframes slideIn {\r\n from {\r\n transform: translateX(400px);\r\n opacity: 0;\r\n }\r\n to {\r\n transform: translateX(0);\r\n opacity: 1;\r\n }\r\n }\r\n\r\n @media (max-width: 600px) {\r\n .toast-container {\r\n top: 10px;\r\n right: 10px;\r\n left: 10px;\r\n }\r\n\r\n .toast {\r\n min-width: auto;\r\n max-width: 100%;\r\n }\r\n }\r\n `]\r\n})\r\nexport class ToastContainerComponent implements OnInit, OnDestroy {\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n\r\n toasts: Toast[] = [];\r\n currentTheme: Theme = 'light';\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private toastService: ToastService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.toastService.toasts\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(toasts => {\r\n this.toasts = toasts;\r\n });\r\n\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n close(id: string) {\r\n this.toastService.remove(id);\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, HostBinding, Output, EventEmitter, inject } from '@angular/core';\nimport { NgIf, NgFor } from '@angular/common';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { MesAuthService, NotificationDto, PagedList, RealTimeNotificationDto } from './mes-auth.service';\nimport { ToastService } from './toast.service';\nimport { ThemeService, Theme } from './theme.service';\nimport { Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\n\n@Component({\n selector: 'ma-notification-panel',\n standalone: true,\n imports: [NgIf, NgFor],\n template: `\n <div class=\"notification-panel\" [class.open]=\"isOpen\">\n <!-- Header -->\n <div class=\"panel-header\">\n <h3>Notifications</h3>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\">✕</button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button\n class=\"tab-btn\"\n [class.active]=\"activeTab === 'unread'\"\n (click)=\"switchTab('unread')\"\n >\n Unread ({{ unreadNotifications.length }})\n </button>\n <button\n class=\"tab-btn\"\n [class.active]=\"activeTab === 'read'\"\n (click)=\"switchTab('read')\"\n >\n Read ({{ readNotifications.length }})\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n <ng-container *ngIf=\"currentNotifications.length > 0\">\n <div\n *ngFor=\"let notification of currentNotifications\"\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ getNotificationMessage(notification) }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels.get(notification.id) }}</span>\n </div>\n </div>\n <button\n class=\"read-btn\"\n (click)=\"markAsRead(notification.id, $event)\"\n title=\"Mark as read\"\n *ngIf=\"!notification.isRead\"\n >\n ✓\n </button>\n <button\n class=\"delete-btn\"\n (click)=\"delete(notification.id, $event)\"\n title=\"Delete notification\"\n *ngIf=\"notification.isRead\"\n >\n 🗑\n </button>\n </div>\n </ng-container>\n\n <ng-container *ngIf=\"currentNotifications.length === 0\">\n <div class=\"empty-state\">\n No {{ activeTab }} notifications\n </div>\n </ng-container>\n </div>\n\n <!-- Footer Actions -->\n <div class=\"panel-footer\" *ngIf=\"currentNotifications.length > 0\">\n <div class=\"footer-actions\" *ngIf=\"activeTab === 'unread'\">\n <button class=\"action-btn\" (click)=\"markAllAsRead()\" *ngIf=\"unreadNotifications.length > 0\">\n Mark all as read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\" *ngIf=\"unreadNotifications.length > 0\">\n Delete all\n </button>\n </div>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\" *ngIf=\"activeTab === 'read' && readNotifications.length > 0\">\n Delete all\n </button>\n </div>\n </div>\n\n <!-- Details Modal -->\n <div class=\"modal-overlay\" *ngIf=\"selectedNotification\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3>{{ selectedNotification.title }}</h3>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\">✕</button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml\"></div>\n <div class=\"modal-footer\">\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n `,\n styles: [`\n :host {\n display: block;\n position: relative;\n --primary-color: #1976d2;\n --primary-hover: #1565c0;\n --success-color: #4caf50;\n --error-color: #f44336;\n --text-primary: #333;\n --text-secondary: #666;\n --text-muted: #999;\n --bg-primary: white;\n --bg-secondary: #f5f5f5;\n --bg-tertiary: #fafafa;\n --bg-hover: #f5f5f5;\n --bg-unread: #e3f2fd;\n --border-color: #e0e0e0;\n --border-light: #f0f0f0;\n --shadow: rgba(0, 0, 0, 0.1);\n }\n\n :host(.theme-dark) {\n display: block;\n position: relative;\n --primary-color: #90caf9;\n --primary-hover: #64b5f6;\n --success-color: #81c784;\n --error-color: #ef5350;\n --text-primary: #e0e0e0;\n --text-secondary: #b0b0b0;\n --text-muted: #888;\n --bg-primary: #1e1e1e;\n --bg-secondary: #2d2d2d;\n --bg-tertiary: #252525;\n --bg-hover: #333;\n --bg-unread: rgba(144, 202, 249, 0.1);\n --border-color: #404040;\n --border-light: #333;\n --shadow: rgba(0, 0, 0, 0.3);\n }\n\n .notification-panel {\n position: fixed;\n top: 0;\n right: -350px;\n width: 350px;\n height: 100vh;\n background: var(--bg-primary);\n box-shadow: -2px 0 8px var(--shadow);\n display: flex;\n flex-direction: column;\n z-index: 1030;\n transition: right 0.3s ease;\n }\n\n .notification-panel.open {\n right: 0;\n }\n\n .panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px;\n border-bottom: 1px solid var(--border-color);\n background-color: var(--bg-secondary);\n }\n\n .panel-header h3 {\n margin: 0;\n font-size: 18px;\n color: var(--text-primary);\n }\n\n .close-btn {\n background: none;\n border: none;\n font-size: 20px;\n cursor: pointer;\n color: var(--text-secondary);\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: color 0.2s;\n }\n\n .close-btn:hover {\n color: var(--text-primary);\n }\n\n .tabs {\n display: flex;\n border-bottom: 1px solid var(--border-color);\n background-color: var(--bg-secondary);\n }\n\n .tab-btn {\n flex: 1;\n padding: 12px 16px;\n background: none;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: all 0.2s;\n border-bottom: 2px solid transparent;\n }\n\n .tab-btn:hover {\n background-color: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .tab-btn.active {\n color: var(--primary-color);\n border-bottom-color: var(--primary-color);\n background-color: var(--bg-primary);\n }\n\n .notifications-list {\n flex: 1;\n overflow-y: auto;\n }\n\n .notification-item {\n display: flex;\n gap: 12px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border-light);\n cursor: pointer;\n background-color: var(--bg-tertiary);\n transition: background-color 0.2s;\n }\n\n .notification-item:hover {\n background-color: var(--bg-hover);\n }\n\n .notification-item.unread {\n background-color: var(--bg-unread);\n }\n\n .notification-content {\n flex: 1;\n min-width: 0;\n }\n\n .notification-title {\n font-weight: 600;\n color: var(--text-primary);\n font-size: 14px;\n margin-bottom: 4px;\n }\n\n .notification-message {\n color: var(--text-secondary);\n font-size: 12px;\n line-height: 1.4;\n margin-bottom: 6px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n\n .notification-meta {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--text-muted);\n }\n\n .app-name {\n font-weight: 500;\n color: var(--primary-color);\n }\n\n .read-btn {\n background: none;\n border: none;\n color: var(--text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: color 0.2s;\n }\n\n .read-btn:hover {\n color: var(--success-color);\n }\n\n .delete-btn {\n background: none;\n border: none;\n color: var(--text-muted);\n cursor: pointer;\n font-size: 14px;\n padding: 0;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n transition: color 0.2s;\n }\n\n .delete-btn:hover {\n color: var(--error-color);\n }\n\n .empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: var(--text-muted);\n font-size: 14px;\n }\n\n .panel-footer {\n padding: 12px 16px;\n border-top: 1px solid var(--border-color);\n background-color: var(--bg-secondary);\n }\n\n .footer-actions {\n display: flex;\n gap: 8px;\n }\n\n .footer-actions .action-btn {\n flex: 1;\n }\n\n .action-btn {\n width: 100%;\n padding: 8px;\n background-color: var(--primary-color);\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-weight: 500;\n transition: background-color 0.2s;\n }\n\n .action-btn:hover {\n background-color: var(--primary-hover);\n }\n\n .delete-all-btn {\n background-color: var(--error-color);\n color: white;\n }\n\n .delete-all-btn:hover {\n background-color: #d32f2f; /* Darker red for hover */\n }\n\n /* Modal Overlay */\n .modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n width: 100vw;\n height: 100vh;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1060;\n }\n\n .modal-container {\n background: var(--bg-primary);\n border-radius: 8px;\n width: 90%;\n max-width: 600px;\n max-height: 80vh;\n display: flex;\n flex-direction: column;\n box-shadow: 0 4px 20px var(--shadow);\n }\n\n .modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n border-bottom: 1px solid var(--border-color);\n background-color: var(--bg-secondary);\n border-radius: 8px 8px 0 0;\n }\n\n .modal-header h3 {\n margin: 0;\n font-size: 18px;\n color: var(--text-primary);\n }\n\n .modal-meta {\n display: flex;\n justify-content: space-between;\n padding: 8px 20px;\n font-size: 12px;\n color: var(--text-muted);\n background-color: var(--bg-tertiary);\n border-bottom: 1px solid var(--border-light);\n }\n\n .modal-body {\n padding: 20px;\n overflow-y: auto;\n flex: 1;\n color: var(--text-primary);\n font-size: 14px;\n line-height: 1.6;\n }\n\n .modal-footer {\n padding: 12px 20px;\n border-top: 1px solid var(--border-color);\n background-color: var(--bg-secondary);\n border-radius: 0 0 8px 8px;\n display: flex;\n justify-content: flex-end;\n }\n\n .modal-footer .action-btn {\n width: auto;\n padding: 8px 24px;\n }\n\n @media (max-width: 600px) {\n .notification-panel {\n width: 100%;\n right: -100%;\n }\n\n .modal-container {\n width: 95%;\n max-height: 90vh;\n }\n }\n `]\n})\nexport class NotificationPanelComponent implements OnInit, OnDestroy {\n @Output() notificationRead = new EventEmitter<void>();\n @HostBinding('class') get themeClass(): string {\n return `theme-${this.currentTheme}`;\n }\n\n isOpen = false;\n notifications: NotificationDto[] = [];\n currentTheme: Theme = 'light';\n activeTab: 'unread' | 'read' = 'unread'; // Default to unread tab\n private destroy$ = new Subject<void>();\n\n // Cached filtered lists — updated explicitly to avoid re-filtering on every CD cycle\n private _unreadNotifications: NotificationDto[] = [];\n private _readNotifications: NotificationDto[] = [];\n\n // Stable time-ago strings keyed by notification id — refreshed every 30s\n dateLabels: Map<string, string> = new Map();\n private dateTimer: ReturnType<typeof setInterval> | null = null;\n\n get unreadNotifications(): NotificationDto[] {\n return this._unreadNotifications;\n }\n\n get readNotifications(): NotificationDto[] {\n return this._readNotifications;\n }\n\n get currentNotifications(): NotificationDto[] {\n return this.activeTab === 'unread' ? this._unreadNotifications : this._readNotifications;\n }\n\n selectedNotification: NotificationDto | null = null;\n selectedNotificationHtml: SafeHtml | null = null;\n selectedNotificationDate: string = '';\n\n // Returns plain text message for list display\n getNotificationMessage(notification: NotificationDto): string {\n return notification.message || '';\n }\n\n private readonly sanitizer = inject(DomSanitizer);\n\n constructor(private authService: MesAuthService, private toastService: ToastService, private themeService: ThemeService) {}\n\n ngOnInit() {\n this.themeService.currentTheme$\n .pipe(takeUntil(this.destroy$))\n .subscribe(theme => {\n this.currentTheme = theme;\n });\n\n this.loadNotifications();\n\n // Refresh time-ago labels every 30s to avoid NG0100 from live new Date() in template\n this.dateTimer = setInterval(() => this.refreshDateLabels(), 30000);\n\n // Listen for new real-time notifications\n this.authService.notifications$\n .pipe(takeUntil(this.destroy$))\n .subscribe((notification: RealTimeNotificationDto) => {\n // Show toast for new notification\n this.toastService.show(\n notification.messageHtml || notification.message || '',\n notification.title,\n 'info',\n 5000\n );\n // Reload notifications list\n this.loadNotifications();\n });\n }\n\n ngOnDestroy() {\n if (this.dateTimer !== null) {\n clearInterval(this.dateTimer);\n this.dateTimer = null;\n }\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n private loadNotifications() {\n this.authService.getNotifications(1, 50, true).subscribe({ // includeRead = true to get both read and unread\n next: (response: PagedList<NotificationDto>) => {\n this.notifications = response.items || [];\n this.onNotificationsChanged();\n },\n error: (err) => {}\n });\n }\n\n open() {\n this.isOpen = true;\n this.activeTab = 'unread'; // Reset to unread tab when opening\n }\n\n close() {\n this.isOpen = false;\n }\n\n switchTab(tab: 'unread' | 'read') {\n this.activeTab = tab;\n }\n\n openDetails(notification: NotificationDto) {\n this.selectedNotification = notification;\n // Cache computed values to avoid re-rendering on every change detection cycle\n const html = notification.messageHtml || notification.message || '';\n this.selectedNotificationHtml = this.sanitizer.bypassSecurityTrustHtml(html);\n this.selectedNotificationDate = this.formatDate(notification.createdAt);\n // Mark as read when opening details (if not already read)\n if (!notification.isRead) {\n this.authService.markAsRead(notification.id).subscribe({\n next: () => {\n notification.isRead = true;\n this.notificationRead.emit();\n this.onNotificationsChanged();\n },\n error: () => {}\n });\n }\n }\n\n closeDetails() {\n this.selectedNotification = null;\n this.selectedNotificationHtml = null;\n this.selectedNotificationDate = '';\n }\n\n markAsRead(notificationId: string, event?: Event) {\n if (event) {\n event.stopPropagation();\n }\n this.authService.markAsRead(notificationId).subscribe({\n next: () => {\n const notification = this.notifications.find(n => n.id === notificationId);\n if (notification) {\n notification.isRead = true;\n this.notificationRead.emit();\n this.onNotificationsChanged();\n }\n },\n error: (err) => {}\n });\n }\n\n markAllAsRead() {\n this.authService.markAllAsRead().subscribe({\n next: () => {\n this.notifications.forEach(n => n.isRead = true);\n this.notificationRead.emit();\n this.onNotificationsChanged();\n },\n error: (err) => {}\n });\n }\n\n deleteAllRead() {\n const readNotificationIds = this.notifications\n .filter(n => n.isRead)\n .map(n => n.id);\n\n // Delete all read notifications\n const deletePromises = readNotificationIds.map(id =>\n this.authService.deleteNotification(id).toPromise()\n );\n\n Promise.all(deletePromises).then(() => {\n // Remove all read notifications from the local array\n this.notifications = this.notifications.filter(n => !n.isRead);\n this.onNotificationsChanged();\n }).catch((err) => {\n // If bulk delete fails, reload notifications to get current state\n this.loadNotifications();\n });\n }\n\n deleteAllUnread() {\n const unreadNotificationIds = this.notifications\n .filter(n => !n.isRead)\n .map(n => n.id);\n\n // Delete all unread notifications\n const deletePromises = unreadNotificationIds.map(id =>\n this.authService.deleteNotification(id).toPromise()\n );\n\n Promise.all(deletePromises).then(() => {\n // Remove all unread notifications from the local array\n this.notifications = this.notifications.filter(n => n.isRead);\n this.notificationRead.emit();\n this.onNotificationsChanged();\n }).catch((err) => {\n // If bulk delete fails, reload notifications to get current state\n this.loadNotifications();\n });\n }\n\n delete(notificationId: string, event: Event) {\n event.stopPropagation();\n const wasUnread = this.notifications.find(n => n.id === notificationId && !n.isRead) !== undefined;\n this.authService.deleteNotification(notificationId).subscribe({\n next: () => {\n this.notifications = this.notifications.filter(n => n.id !== notificationId);\n if (wasUnread) {\n this.notificationRead.emit();\n }\n this.onNotificationsChanged();\n },\n error: (err) => {}\n });\n }\n\n formatDate(dateString: string): string {\n return this.computeTimeAgo(dateString, new Date());\n }\n\n // Pure computation — takes now as param so it never calls new Date() internally\n private computeTimeAgo(dateString: string, now: Date): string {\n const normalizedDateString = this.parseUtcDate(dateString);\n const date = new Date(normalizedDateString);\n\n if (isNaN(date.getTime())) {\n return 'Invalid date';\n }\n\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n\n if (diffMins < 1) return 'Now';\n if (diffMins < 60) return `${diffMins}m ago`;\n if (diffHours < 24) return `${diffHours}h ago`;\n if (diffDays < 7) return `${diffDays}d ago`;\n\n return date.toLocaleDateString();\n }\n\n // Rebuild dateLabels map using a single shared now — prevents mid-loop clock drift\n private refreshDateLabels(): void {\n const now = new Date();\n for (const n of this.notifications) {\n this.dateLabels.set(n.id, this.computeTimeAgo(n.createdAt, now));\n }\n }\n\n // Re-run filter once and store results in stable arrays\n private recomputeFilteredLists(): void {\n this._unreadNotifications = this.notifications.filter(n => !n.isRead);\n this._readNotifications = this.notifications.filter(n => n.isRead);\n }\n\n // Single call-site after every notification mutation\n private onNotificationsChanged(): void {\n this.recomputeFilteredLists();\n this.refreshDateLabels();\n }\n\n // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)\n private parseUtcDate(dateStr: string): string {\n // Handle date strings that might be missing the 'T' separator\n // Convert formats like \"2023-12-01 12:30:45\" to \"2023-12-01T12:30:45\"\n let normalized = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T');\n\n // If no timezone indicator, assume UTC by appending 'Z'\n if (!normalized.endsWith('Z') && !normalized.includes('+') && !normalized.includes('-', 10)) {\n normalized += 'Z';\n }\n\n return normalized;\n }\n}\n","import { Component, ViewChild, AfterViewInit } from '@angular/core';\r\nimport { ToastContainerComponent } from './toast-container.component';\r\nimport { UserProfileComponent } from './user-profile.component';\r\nimport { NotificationPanelComponent } from './notification-panel.component';\r\n\r\n@Component({\r\n selector: 'ma-user',\r\n standalone: true,\r\n imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent],\r\n template: `\r\n <ma-toast-container></ma-toast-container>\r\n <div class=\"user-header\">\r\n <ma-user-profile (notificationClick)=\"notificationPanel.open()\"></ma-user-profile>\r\n </div>\r\n <ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\r\n `,\r\n styles: [`\r\n .user-header {\r\n display: flex;\r\n justify-content: flex-end;\r\n }\r\n `]\r\n})\r\nexport class MaUserComponent implements AfterViewInit {\r\n @ViewChild(UserProfileComponent) userProfile?: UserProfileComponent;\r\n\r\n ngAfterViewInit() {\r\n // Ensure proper initialization\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n\r\n onNotificationRead() {\r\n if (this.userProfile) {\r\n this.userProfile.loadUnreadCount();\r\n }\r\n }\r\n}\r\n","import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding } from '@angular/core';\r\nimport { NgIf } from '@angular/common';\r\nimport { MesAuthService } from './mes-auth.service';\r\nimport { ThemeService, Theme } from './theme.service';\r\nimport { Subject } from 'rxjs';\r\nimport { takeUntil } from 'rxjs/operators';\r\n\r\n@Component({\r\n selector: 'ma-notification-badge',\r\n standalone: true,\r\n imports: [NgIf],\r\n template: `\r\n <button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\r\n <span class=\"icon\">🔔</span>\r\n <span class=\"badge\" *ngIf=\"unreadCount > 0\">{{ unreadCount }}</span>\r\n </button>\r\n `,\r\n styles: [`\r\n :host {\r\n --error-color: #f44336;\r\n }\r\n\r\n :host(.theme-dark) {\r\n --error-color: #ef5350;\r\n }\r\n\r\n .notification-btn {\r\n position: relative;\r\n background: none;\r\n border: none;\r\n font-size: 24px;\r\n cursor: pointer;\r\n padding: 8px;\r\n transition: opacity 0.2s;\r\n }\r\n\r\n .notification-btn:hover {\r\n opacity: 0.7;\r\n }\r\n\r\n .icon {\r\n display: inline-block;\r\n }\r\n\r\n .badge {\r\n position: absolute;\r\n top: 0;\r\n right: 0;\r\n background-color: var(--error-color);\r\n color: white;\r\n border-radius: 50%;\r\n width: 20px;\r\n height: 20px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 12px;\r\n font-weight: bold;\r\n }\r\n `]\r\n})\r\nexport class NotificationBadgeComponent implements OnInit, OnDestroy {\r\n @Output() notificationClick = new EventEmitter<void>();\r\n @HostBinding('class') get themeClass(): string {\r\n return `theme-${this.currentTheme}`;\r\n }\r\n \r\n unreadCount = 0;\r\n currentTheme: Theme = 'light';\r\n private hasUser = false;\r\n private destroy$ = new Subject<void>();\r\n\r\n constructor(private authService: MesAuthService, private themeService: ThemeService) {}\r\n\r\n ngOnInit() {\r\n this.themeService.currentTheme$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(theme => {\r\n this.currentTheme = theme;\r\n });\r\n\r\n this.authService.currentUser$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(user => {\r\n this.hasUser = !!user;\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n this.loadUnreadCount();\r\n });\r\n \r\n // Listen for new notifications\r\n this.authService.notifications$\r\n .pipe(takeUntil(this.destroy$))\r\n .subscribe(() => {\r\n if (this.hasUser) {\r\n this.loadUnreadCount();\r\n }\r\n });\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n private loadUnreadCount() {\r\n if (!this.hasUser) {\r\n this.unreadCount = 0;\r\n return;\r\n }\r\n\r\n this.authService.getUnreadCount().subscribe({\r\n next: (response: any) => {\r\n this.unreadCount = response.unreadCount || 0;\r\n },\r\n error: (err) => console.error('Error loading unread count:', err)\r\n });\r\n }\r\n\r\n onNotificationClick() {\r\n this.notificationClick.emit();\r\n }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.MesAuthService","i3.ThemeService","i1.ToastService","i2.ThemeService","i2.ToastService"],"mappings":"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"}
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, EnvironmentProviders, OnDestroy, OnInit, EventEmitter, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
|
2
|
+
import { InjectionToken, NgZone, EnvironmentProviders, OnDestroy, OnInit, EventEmitter, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
|
3
3
|
import { HttpClient, HttpInterceptorFn } from '@angular/common/http';
|
|
4
4
|
import { Observable } from 'rxjs';
|
|
5
5
|
import { Router } from '@angular/router';
|
|
@@ -156,8 +156,9 @@ declare class MesAuthService {
|
|
|
156
156
|
private config;
|
|
157
157
|
private http;
|
|
158
158
|
private router?;
|
|
159
|
+
private ngZone;
|
|
159
160
|
constructor();
|
|
160
|
-
init(config: MesAuthConfig, httpClient: HttpClient, router?: Router): void;
|
|
161
|
+
init(config: MesAuthConfig, httpClient: HttpClient, router?: Router, ngZone?: NgZone): void;
|
|
161
162
|
getConfig(): MesAuthConfig | null;
|
|
162
163
|
private fetchCurrentUser;
|
|
163
164
|
getUnreadCount(): Observable<any>;
|
|
@@ -346,6 +347,10 @@ declare class NotificationPanelComponent implements OnInit, OnDestroy {
|
|
|
346
347
|
currentTheme: Theme;
|
|
347
348
|
activeTab: 'unread' | 'read';
|
|
348
349
|
private destroy$;
|
|
350
|
+
private _unreadNotifications;
|
|
351
|
+
private _readNotifications;
|
|
352
|
+
dateLabels: Map<string, string>;
|
|
353
|
+
private dateTimer;
|
|
349
354
|
get unreadNotifications(): NotificationDto[];
|
|
350
355
|
get readNotifications(): NotificationDto[];
|
|
351
356
|
get currentNotifications(): NotificationDto[];
|
|
@@ -369,6 +374,10 @@ declare class NotificationPanelComponent implements OnInit, OnDestroy {
|
|
|
369
374
|
deleteAllUnread(): void;
|
|
370
375
|
delete(notificationId: string, event: Event): void;
|
|
371
376
|
formatDate(dateString: string): string;
|
|
377
|
+
private computeTimeAgo;
|
|
378
|
+
private refreshDateLabels;
|
|
379
|
+
private recomputeFilteredLists;
|
|
380
|
+
private onNotificationsChanged;
|
|
372
381
|
private parseUtcDate;
|
|
373
382
|
static ɵfac: i0.ɵɵFactoryDeclaration<NotificationPanelComponent, never>;
|
|
374
383
|
static ɵcmp: i0.ɵɵComponentDeclaration<NotificationPanelComponent, "ma-notification-panel", never, {}, { "notificationRead": "notificationRead"; }, never, never, true, never>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mesauth-angular",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "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",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|