mesauth-angular 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,361 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Component, Output, EventEmitter } from '@angular/core';
11
+ import { CommonModule } from '@angular/common';
12
+ import { Router } from '@angular/router';
13
+ import { MesAuthService } from './mes-auth.service';
14
+ import { Subject } from 'rxjs';
15
+ import { takeUntil } from 'rxjs/operators';
16
+ let UserProfileComponent = class UserProfileComponent {
17
+ constructor(authService, router) {
18
+ this.authService = authService;
19
+ this.router = router;
20
+ this.notificationClick = new EventEmitter();
21
+ this.currentUser = null;
22
+ this.unreadCount = 0;
23
+ this.dropdownOpen = false;
24
+ this.destroy$ = new Subject();
25
+ }
26
+ ngOnInit() {
27
+ this.authService.currentUser$
28
+ .pipe(takeUntil(this.destroy$))
29
+ .subscribe(user => {
30
+ this.currentUser = user;
31
+ });
32
+ this.loadUnreadCount();
33
+ // Listen for new notifications
34
+ this.authService.notifications$
35
+ .pipe(takeUntil(this.destroy$))
36
+ .subscribe(() => {
37
+ this.loadUnreadCount();
38
+ });
39
+ }
40
+ ngOnDestroy() {
41
+ this.destroy$.next();
42
+ this.destroy$.complete();
43
+ }
44
+ loadUnreadCount() {
45
+ this.authService.getUnreadCount().subscribe({
46
+ next: (response) => {
47
+ this.unreadCount = response.unreadCount || 0;
48
+ },
49
+ error: (err) => console.error('Error loading unread count:', err)
50
+ });
51
+ }
52
+ getAvatarUrl(user) {
53
+ const config = this.authService.getConfig();
54
+ const baseUrl = (config === null || config === void 0 ? void 0 : config.avatarUrl) || 'https://ui-avatars.com/api/';
55
+ // Use userName first, fallback to userId, final fallback to 'User'
56
+ const displayName = user.userName || user.userId || 'User';
57
+ return `${baseUrl}?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;
58
+ }
59
+ getLastNameInitial(user) {
60
+ const fullName = user.fullName || user.userName || 'U';
61
+ const parts = fullName.split(' ');
62
+ const lastPart = parts[parts.length - 1];
63
+ return lastPart.charAt(0).toUpperCase();
64
+ }
65
+ toggleDropdown() {
66
+ this.dropdownOpen = !this.dropdownOpen;
67
+ }
68
+ onLogin() {
69
+ const config = this.authService.getConfig();
70
+ const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
71
+ const returnUrl = encodeURIComponent(this.router.url);
72
+ this.router.navigateByUrl(`${baseUrl}/login?returnUrl=${returnUrl}`);
73
+ }
74
+ onViewProfile() {
75
+ const config = this.authService.getConfig();
76
+ const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
77
+ this.router.navigateByUrl(`${baseUrl}/profile`);
78
+ this.dropdownOpen = false;
79
+ }
80
+ onLogout() {
81
+ this.authService.logout().subscribe({
82
+ next: () => {
83
+ // Clear current user after successful logout
84
+ this.currentUser = null;
85
+ this.dropdownOpen = false;
86
+ // Navigate to login with return URL
87
+ const config = this.authService.getConfig();
88
+ const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
89
+ const returnUrl = encodeURIComponent(this.router.url);
90
+ this.router.navigateByUrl(`${baseUrl}/login?returnUrl=${returnUrl}`);
91
+ },
92
+ error: (err) => {
93
+ console.error('Logout error:', err);
94
+ // Still navigate to login even if logout fails
95
+ const config = this.authService.getConfig();
96
+ const baseUrl = (config === null || config === void 0 ? void 0 : config.userBaseUrl) || '';
97
+ this.router.navigateByUrl(`${baseUrl}/login`);
98
+ }
99
+ });
100
+ }
101
+ onNotificationClick() {
102
+ this.notificationClick.emit();
103
+ }
104
+ };
105
+ __decorate([
106
+ Output(),
107
+ __metadata("design:type", Object)
108
+ ], UserProfileComponent.prototype, "notificationClick", void 0);
109
+ UserProfileComponent = __decorate([
110
+ Component({
111
+ selector: 'ma-user-profile',
112
+ standalone: true,
113
+ imports: [CommonModule],
114
+ template: `
115
+ <div class="user-profile-container">
116
+ <!-- Not logged in -->
117
+ <ng-container *ngIf="!currentUser">
118
+ <button class="login-btn" (click)="onLogin()">
119
+ Login
120
+ </button>
121
+ </ng-container>
122
+
123
+ <!-- Logged in -->
124
+ <ng-container *ngIf="currentUser">
125
+ <div class="user-header">
126
+ <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
127
+ <span class="icon">🔔</span>
128
+ <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
129
+ </button>
130
+
131
+ <div class="user-menu-wrapper">
132
+ <button class="user-menu-btn" (click)="toggleDropdown()">
133
+ <img
134
+ *ngIf="currentUser.fullName || currentUser.userName"
135
+ [src]="getAvatarUrl(currentUser)"
136
+ [alt]="currentUser.fullName || currentUser.userName"
137
+ class="avatar"
138
+ />
139
+ <span *ngIf="!(currentUser.fullName || currentUser.userName)" class="avatar-initial">
140
+ {{ getLastNameInitial(currentUser) }}
141
+ </span>
142
+ </button>
143
+
144
+ <div class="dropdown-menu" *ngIf="dropdownOpen">
145
+ <div class="dropdown-header">
146
+ {{ currentUser.fullName || currentUser.userName }}
147
+ </div>
148
+ <button class="dropdown-item profile-link" (click)="onViewProfile()">
149
+ View Profile
150
+ </button>
151
+ <button class="dropdown-item logout-item" (click)="onLogout()">
152
+ Logout
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </ng-container>
158
+ </div>
159
+ `,
160
+ styles: [`
161
+ .user-profile-container {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 16px;
165
+ padding: 0 16px;
166
+ }
167
+
168
+ .login-btn {
169
+ padding: 8px 16px;
170
+ background-color: #1976d2;
171
+ color: white;
172
+ border: none;
173
+ border-radius: 4px;
174
+ cursor: pointer;
175
+ font-weight: 500;
176
+ transition: background-color 0.3s;
177
+ }
178
+
179
+ .login-btn:hover {
180
+ background-color: #1565c0;
181
+ }
182
+
183
+ .user-header {
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 16px;
187
+ }
188
+
189
+ .notification-btn {
190
+ position: relative;
191
+ background: none;
192
+ border: none;
193
+ font-size: 24px;
194
+ cursor: pointer;
195
+ padding: 8px;
196
+ transition: opacity 0.2s;
197
+ }
198
+
199
+ .notification-btn:hover {
200
+ opacity: 0.7;
201
+ }
202
+
203
+ .icon {
204
+ display: inline-block;
205
+ }
206
+
207
+ .badge {
208
+ position: absolute;
209
+ top: 0;
210
+ right: 0;
211
+ background-color: #f44336;
212
+ color: white;
213
+ border-radius: 50%;
214
+ width: 20px;
215
+ height: 20px;
216
+ display: flex;
217
+ align-items: center;
218
+ justify-content: center;
219
+ font-size: 12px;
220
+ font-weight: bold;
221
+ }
222
+
223
+ .user-menu-wrapper {
224
+ position: relative;
225
+ }
226
+
227
+ .user-menu-btn {
228
+ background: none;
229
+ border: none;
230
+ cursor: pointer;
231
+ padding: 4px;
232
+ border-radius: 50%;
233
+ transition: background-color 0.2s;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ }
238
+
239
+ .user-menu-btn:hover {
240
+ background-color: rgba(25, 118, 210, 0.1);
241
+ }
242
+
243
+ .avatar {
244
+ width: 40px;
245
+ height: 40px;
246
+ border-radius: 50%;
247
+ object-fit: cover;
248
+ background-color: #e0e0e0;
249
+ }
250
+
251
+ .avatar-initial {
252
+ width: 40px;
253
+ height: 40px;
254
+ border-radius: 50%;
255
+ background-color: #1976d2;
256
+ color: white;
257
+ display: flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ font-weight: bold;
261
+ font-size: 16px;
262
+ }
263
+
264
+ .dropdown-menu {
265
+ position: absolute;
266
+ top: calc(100% + 8px);
267
+ right: 0;
268
+ background: white;
269
+ border: 1px solid #e0e0e0;
270
+ border-radius: 4px;
271
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
272
+ min-width: 200px;
273
+ z-index: 1000;
274
+ overflow: hidden;
275
+ }
276
+
277
+ .dropdown-header {
278
+ padding: 12px 16px;
279
+ border-bottom: 1px solid #f0f0f0;
280
+ font-weight: 600;
281
+ color: #333;
282
+ font-size: 14px;
283
+ }
284
+
285
+ .dropdown-item {
286
+ display: block;
287
+ width: 100%;
288
+ padding: 12px 16px;
289
+ border: none;
290
+ background: none;
291
+ text-align: left;
292
+ cursor: pointer;
293
+ font-size: 14px;
294
+ color: #333;
295
+ text-decoration: none;
296
+ transition: background-color 0.2s;
297
+ }
298
+
299
+ .dropdown-item:hover {
300
+ background-color: #f5f5f5;
301
+ }
302
+
303
+ .profile-link {
304
+ color: #1976d2;
305
+ }
306
+
307
+ .logout-item {
308
+ border-top: 1px solid #f0f0f0;
309
+ color: #f44336;
310
+ }
311
+
312
+ .logout-item:hover {
313
+ background-color: #ffebee;
314
+ }
315
+
316
+ .user-info {
317
+ display: flex;
318
+ flex-direction: column;
319
+ gap: 2px;
320
+ }
321
+
322
+ .user-name {
323
+ font-weight: 500;
324
+ font-size: 14px;
325
+ color: #333;
326
+ }
327
+
328
+ .user-position {
329
+ font-size: 12px;
330
+ color: #666;
331
+ }
332
+
333
+ .logout-btn {
334
+ background: none;
335
+ border: none;
336
+ font-size: 20px;
337
+ cursor: pointer;
338
+ color: #666;
339
+ padding: 4px 8px;
340
+ transition: color 0.2s;
341
+ }
342
+
343
+ .logout-btn:hover {
344
+ color: #1976d2;
345
+ }
346
+
347
+ @media (max-width: 768px) {
348
+ .user-info {
349
+ display: none;
350
+ }
351
+
352
+ .avatar {
353
+ width: 32px;
354
+ height: 32px;
355
+ }
356
+ }
357
+ `]
358
+ }),
359
+ __metadata("design:paramtypes", [MesAuthService, Router])
360
+ ], UserProfileComponent);
361
+ export { UserProfileComponent };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "mesauth-angular",
3
+ "version": "0.1.0",
4
+ "description": "Angular-friendly SignalR notifier + API helper for current user and notifications",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": ["dist"],
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json",
10
+ "prepare": "npm run build",
11
+ "publish": "npm publish"
12
+ },
13
+ "keywords": ["angular","signalr","notifications","auth","mes"],
14
+ "license": "MIT",
15
+ "peerDependencies": {
16
+ "@angular/core": "^9 || ^10 || ^11 || ^12 || ^13 || ^14 || ^15",
17
+ "@angular/common": "^9 || ^10 || ^11 || ^12 || ^13 || ^14 || ^15",
18
+ "@angular/router": "^9 || ^10 || ^11 || ^12 || ^13 || ^14 || ^15",
19
+ "rxjs": "^6 || ^7"
20
+ },
21
+ "dependencies": {
22
+ "@microsoft/signalr": "^7.0.0",
23
+ "rxjs": "^6.6.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "~4.9.5",
27
+ "tslib": "^2.0.0"
28
+ }
29
+ }