mesauth-angular 1.2.0 → 1.2.1

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.
@@ -1,449 +0,0 @@
1
- import { Component, OnInit, OnDestroy, Output, EventEmitter, HostBinding, HostListener, signal, ChangeDetectorRef } from '@angular/core';
2
- import { NgIf } from '@angular/common';
3
- import { Router } from '@angular/router';
4
- import { MesAuthService, IUser } from './mes-auth.service';
5
- import { ThemeService, Theme } from './theme.service';
6
- import { Subject } from 'rxjs';
7
- import { takeUntil } from 'rxjs/operators';
8
-
9
- @Component({
10
- selector: 'ma-user-profile',
11
- standalone: true,
12
- imports: [NgIf],
13
- template: `
14
- <div class="user-profile-container">
15
- <!-- Not logged in -->
16
- <ng-container *ngIf="!currentUser()">
17
- <button class="login-btn" (click)="onLogin()">
18
- Login
19
- </button>
20
- </ng-container>
21
-
22
- <!-- Logged in -->
23
- <ng-container *ngIf="currentUser()">
24
- <div class="user-header">
25
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
26
- <span class="icon">🔔</span>
27
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
28
- </button>
29
-
30
- <div class="user-menu-wrapper">
31
- <button class="user-menu-btn" (click)="toggleDropdown()">
32
- <img
33
- *ngIf="currentUser().fullName || currentUser().userName"
34
- [src]="getAvatarUrl(currentUser())"
35
- [alt]="currentUser().fullName || currentUser().userName"
36
- class="avatar"
37
- />
38
- <span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
39
- {{ getLastNameInitial(currentUser()) }}
40
- </span>
41
- </button>
42
-
43
- <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
44
- <div class="mes-dropdown-header">
45
- {{ currentUser().fullName || currentUser().userName }}
46
- </div>
47
- <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
48
- View Profile
49
- </button>
50
- <button class="mes-dropdown-item logout-item" (click)="onLogout()">
51
- Logout
52
- </button>
53
- </div>
54
- </div>
55
- </div>
56
- </ng-container>
57
- </div>
58
- `,
59
- styles: [`
60
- :host {
61
- --primary-color: #1976d2;
62
- --primary-hover: #1565c0;
63
- --primary-light: rgba(25, 118, 210, 0.1);
64
- --error-color: #f44336;
65
- --error-light: #ffebee;
66
- --text-primary: #333;
67
- --text-secondary: #666;
68
- --text-muted: #999;
69
- --bg-primary: white;
70
- --bg-secondary: #f5f5f5;
71
- --bg-tertiary: #fafafa;
72
- --bg-hover: #f5f5f5;
73
- --border-color: #e0e0e0;
74
- --border-light: #f0f0f0;
75
- --shadow: rgba(0, 0, 0, 0.15);
76
- --shadow-light: rgba(0, 0, 0, 0.1);
77
- }
78
-
79
- :host(.theme-dark) {
80
- --primary-color: #90caf9;
81
- --primary-hover: #64b5f6;
82
- --primary-light: rgba(144, 202, 249, 0.1);
83
- --error-color: #ef5350;
84
- --error-light: rgba(239, 83, 80, 0.1);
85
- --text-primary: #e0e0e0;
86
- --text-secondary: #b0b0b0;
87
- --text-muted: #888;
88
- --bg-primary: #1e1e1e;
89
- --bg-secondary: #2d2d2d;
90
- --bg-tertiary: #252525;
91
- --bg-hover: #333;
92
- --border-color: #404040;
93
- --border-light: #333;
94
- --shadow: rgba(0, 0, 0, 0.3);
95
- --shadow-light: rgba(0, 0, 0, 0.2);
96
- }
97
-
98
- .user-profile-container {
99
- display: flex;
100
- align-items: center;
101
- gap: 16px;
102
- padding: 0 16px;
103
- }
104
-
105
- .login-btn {
106
- padding: 8px 16px;
107
- background-color: var(--primary-color);
108
- color: white;
109
- border: none;
110
- border-radius: 4px;
111
- cursor: pointer;
112
- font-weight: 500;
113
- transition: background-color 0.3s;
114
- }
115
-
116
- .login-btn:hover {
117
- background-color: var(--primary-hover);
118
- }
119
-
120
- .user-header {
121
- display: flex;
122
- align-items: center;
123
- gap: 16px;
124
- }
125
-
126
- .notification-btn {
127
- position: relative;
128
- background: none;
129
- border: none;
130
- font-size: 24px;
131
- cursor: pointer;
132
- padding: 8px;
133
- transition: opacity 0.2s;
134
- }
135
-
136
- .notification-btn:hover {
137
- opacity: 0.7;
138
- }
139
-
140
- .icon {
141
- display: inline-block;
142
- }
143
-
144
- .badge {
145
- position: absolute;
146
- top: 0;
147
- right: 0;
148
- background-color: var(--error-color);
149
- color: white;
150
- border-radius: 50%;
151
- width: 20px;
152
- height: 20px;
153
- display: flex;
154
- align-items: center;
155
- justify-content: center;
156
- font-size: 12px;
157
- font-weight: bold;
158
- }
159
-
160
- .user-menu-wrapper {
161
- position: relative;
162
- }
163
-
164
- .user-menu-btn {
165
- background: none;
166
- border: none;
167
- cursor: pointer;
168
- padding: 4px;
169
- border-radius: 50%;
170
- transition: background-color 0.2s;
171
- display: flex;
172
- align-items: center;
173
- justify-content: center;
174
- }
175
-
176
- .user-menu-btn:hover {
177
- background-color: var(--primary-light);
178
- }
179
-
180
- .avatar {
181
- width: 40px;
182
- height: 40px;
183
- border-radius: 50%;
184
- object-fit: cover;
185
- background-color: #e0e0e0;
186
- }
187
-
188
- .avatar-initial {
189
- width: 40px;
190
- height: 40px;
191
- border-radius: 50%;
192
- background-color: var(--primary-color);
193
- color: white;
194
- display: flex;
195
- align-items: center;
196
- justify-content: center;
197
- font-weight: bold;
198
- font-size: 16px;
199
- }
200
-
201
- .mes-dropdown-menu {
202
- position: absolute;
203
- top: calc(100% + 8px);
204
- right: 0;
205
- background: var(--bg-primary);
206
- border: 1px solid var(--border-color);
207
- border-radius: 4px;
208
- box-shadow: 0 2px 8px var(--shadow);
209
- min-width: 200px;
210
- z-index: 1000;
211
- overflow: hidden;
212
- }
213
-
214
- .mes-dropdown-header {
215
- padding: 12px 16px;
216
- border-bottom: 1px solid var(--border-light);
217
- font-weight: 600;
218
- color: var(--text-primary);
219
- font-size: 14px;
220
- }
221
-
222
- .mes-dropdown-item {
223
- display: block;
224
- width: 100%;
225
- padding: 12px 16px;
226
- border: none;
227
- background: none;
228
- text-align: left;
229
- cursor: pointer;
230
- font-size: 14px;
231
- color: var(--text-primary);
232
- text-decoration: none;
233
- transition: background-color 0.2s;
234
- }
235
-
236
- .mes-dropdown-item:hover {
237
- background-color: var(--bg-hover);
238
- }
239
-
240
- .profile-link {
241
- color: var(--primary-color);
242
- }
243
-
244
- .logout-item {
245
- border-top: 1px solid var(--border-light);
246
- color: var(--error-color);
247
- }
248
-
249
- .logout-item:hover {
250
- background-color: var(--error-light);
251
- }
252
-
253
- .user-info {
254
- display: flex;
255
- flex-direction: column;
256
- gap: 2px;
257
- }
258
-
259
- .user-name {
260
- font-weight: 500;
261
- font-size: 14px;
262
- color: var(--text-primary);
263
- }
264
-
265
- .user-position {
266
- font-size: 12px;
267
- color: var(--text-secondary);
268
- }
269
-
270
- .logout-btn {
271
- background: none;
272
- border: none;
273
- font-size: 20px;
274
- cursor: pointer;
275
- color: var(--text-secondary);
276
- padding: 4px 8px;
277
- transition: color 0.2s;
278
- }
279
-
280
- .logout-btn:hover {
281
- color: var(--primary-color);
282
- }
283
-
284
- @media (max-width: 768px) {
285
- .user-info {
286
- display: none;
287
- }
288
-
289
- .avatar {
290
- width: 32px;
291
- height: 32px;
292
- }
293
- }
294
- `]
295
- })
296
- export class UserProfileComponent implements OnInit, OnDestroy {
297
- @Output() notificationClick = new EventEmitter<void>();
298
- @HostBinding('class') get themeClass(): string {
299
- return `theme-${this.currentTheme}`;
300
- }
301
-
302
- currentUser = signal<IUser | null>(null);
303
- currentTheme: Theme = 'light';
304
- unreadCount = 0;
305
- dropdownOpen = false;
306
- private hasUser = false;
307
- private destroy$ = new Subject<void>();
308
-
309
- // Signal to force avatar refresh
310
- avatarRefresh = signal<number>(Date.now());
311
-
312
- constructor(private authService: MesAuthService, private router: Router, private themeService: ThemeService, private cdr: ChangeDetectorRef) {}
313
-
314
- ngOnInit() {
315
- this.authService.currentUser$
316
- .pipe(takeUntil(this.destroy$))
317
- .subscribe(user => {
318
- this.currentUser.set(user);
319
- this.hasUser = !!user;
320
- // Force avatar refresh when user changes
321
- this.avatarRefresh.set(Date.now());
322
- if (!this.hasUser) {
323
- this.unreadCount = 0;
324
- } else {
325
- this.loadUnreadCount();
326
- }
327
- this.cdr.markForCheck();
328
- });
329
-
330
- this.themeService.currentTheme$
331
- .pipe(takeUntil(this.destroy$))
332
- .subscribe(theme => {
333
- this.currentTheme = theme;
334
- });
335
-
336
- // Listen for new real-time notifications (SignalR only)
337
- this.authService.notifications$
338
- .pipe(takeUntil(this.destroy$))
339
- .subscribe(() => {
340
- if (this.hasUser) {
341
- this.loadUnreadCount();
342
- }
343
- });
344
- }
345
-
346
- ngOnDestroy() {
347
- this.destroy$.next();
348
- this.destroy$.complete();
349
- }
350
-
351
- loadUnreadCount() {
352
- if (!this.hasUser) {
353
- this.unreadCount = 0;
354
- return;
355
- }
356
-
357
- this.authService.getUnreadCount().subscribe({
358
- next: (response: any) => {
359
- this.unreadCount = response.unreadCount || 0;
360
- },
361
- error: (err) => {}
362
- });
363
- }
364
-
365
- getAvatarUrl(user: IUser): string {
366
- // Use the refresh signal to force update
367
- const refresh = this.avatarRefresh();
368
- const config = this.authService.getConfig();
369
- const baseUrl = config?.apiBaseUrl || '';
370
-
371
- // If user has avatarPath, use it directly
372
- if (user.avatarPath) {
373
- // If avatarPath is already a full URL, use it as-is
374
- if (user.avatarPath.startsWith('http://') || user.avatarPath.startsWith('https://')) {
375
- return user.avatarPath;
376
- }
377
- // If it's a relative path, construct full URL with refresh timestamp
378
- return `${baseUrl.replace(/\/$/, '')}${user.avatarPath}?t=${refresh}`;
379
- }
380
-
381
- // Fallback: construct URL using userId
382
- const userId = user.userId;
383
- if (userId && baseUrl) {
384
- return `${baseUrl.replace(/\/$/, '')}/auth/${userId}/avatar?t=${refresh}`;
385
- }
386
-
387
- // Fallback to UI avatars service if no userId or baseUrl
388
- const displayName = user.userName || user.userId || 'User';
389
- return `https://ui-avatars.com/api/?name=${encodeURIComponent(displayName)}&background=1976d2&color=fff`;
390
- }
391
-
392
- getLastNameInitial(user: IUser): string {
393
- const fullName = user.fullName || user.userName || 'U';
394
- const parts = fullName.split(' ');
395
- const lastPart = parts[parts.length - 1];
396
- return lastPart.charAt(0).toUpperCase();
397
- }
398
-
399
- toggleDropdown() {
400
- this.dropdownOpen = !this.dropdownOpen;
401
- }
402
-
403
- @HostListener('document:click', ['$event'])
404
- onDocumentClick(event: Event) {
405
- const target = event.target as HTMLElement;
406
- const clickedInside = target.closest('.user-menu-wrapper');
407
- if (!clickedInside) {
408
- this.dropdownOpen = false;
409
- }
410
- }
411
-
412
- onLogin() {
413
- const config = this.authService.getConfig();
414
- const baseUrl = config?.userBaseUrl || '';
415
- const returnUrl = encodeURIComponent(this.router.url);
416
- window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
417
- }
418
-
419
- onViewProfile() {
420
- this.router.navigate(['/profile']);
421
- this.dropdownOpen = false;
422
- }
423
-
424
- onLogout() {
425
- this.authService.logout().subscribe({
426
- next: () => {
427
- // Clear current user after successful logout
428
- this.dropdownOpen = false;
429
-
430
- // Navigate to login with return URL
431
- const config = this.authService.getConfig();
432
- const baseUrl = config?.userBaseUrl || '';
433
- const returnUrl = encodeURIComponent(window.location.href);
434
- window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
435
- },
436
- error: (err) => {
437
- // Still navigate to login even if logout fails
438
- const config = this.authService.getConfig();
439
- const baseUrl = config?.userBaseUrl || '';
440
- window.location.href = `${baseUrl}/login`;
441
- }
442
- });
443
- }
444
-
445
- onNotificationClick() {
446
- this.notificationClick.emit();
447
- }
448
- }
449
-
package/tsconfig.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2020",
4
- "module": "es2020",
5
- "lib": ["es2020", "dom"],
6
- "declaration": true,
7
- "outDir": "dist",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "moduleResolution": "node",
13
- "allowSyntheticDefaultImports": true,
14
- "experimentalDecorators": true,
15
- "emitDecoratorMetadata": true
16
- },
17
- "angularCompilerOptions": {
18
- "enableIvy": true
19
- },
20
- "include": ["src/**/*"],
21
- "exclude": ["node_modules", "dist"]
22
- }
File without changes
File without changes