@noatgnu/cupcake-core 1.2.13 → 1.2.15
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.
|
@@ -2,9 +2,9 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, InjectionToken, Injectable, signal, computed, Component, effect, ChangeDetectorRef, NgModule } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common/http';
|
|
4
4
|
import { HttpClient, HttpParams, provideHttpClient, withInterceptors, HttpClientModule } from '@angular/common/http';
|
|
5
|
-
import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject, timer, EMPTY, debounceTime, distinctUntilChanged } from 'rxjs';
|
|
5
|
+
import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject, interval, timer, EMPTY, debounceTime, distinctUntilChanged } from 'rxjs';
|
|
6
6
|
import { Router, ActivatedRoute, RouterModule } from '@angular/router';
|
|
7
|
-
import { map as map$1,
|
|
7
|
+
import { map as map$1, tap as tap$1, takeUntil, switchMap as switchMap$1 } from 'rxjs/operators';
|
|
8
8
|
import * as i1$1 from '@angular/forms';
|
|
9
9
|
import { FormBuilder, Validators, ReactiveFormsModule, FormsModule, NonNullableFormBuilder } from '@angular/forms';
|
|
10
10
|
import * as i2 from '@angular/common';
|
|
@@ -455,10 +455,10 @@ class AuthService {
|
|
|
455
455
|
this.currentUserSubject.next(null);
|
|
456
456
|
this.isAuthenticatedSubject.next(false);
|
|
457
457
|
}
|
|
458
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
459
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
458
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
459
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
460
460
|
}
|
|
461
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
461
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AuthService, decorators: [{
|
|
462
462
|
type: Injectable,
|
|
463
463
|
args: [{
|
|
464
464
|
providedIn: 'root'
|
|
@@ -601,10 +601,10 @@ class ResourceService {
|
|
|
601
601
|
}
|
|
602
602
|
return prepared;
|
|
603
603
|
}
|
|
604
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
605
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
604
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ResourceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
605
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ResourceService, providedIn: 'root' });
|
|
606
606
|
}
|
|
607
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
607
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ResourceService, decorators: [{
|
|
608
608
|
type: Injectable,
|
|
609
609
|
args: [{
|
|
610
610
|
providedIn: 'root'
|
|
@@ -999,10 +999,10 @@ class ApiService {
|
|
|
999
999
|
testRemoteHostConnection(id) {
|
|
1000
1000
|
return this.http.post(`${this.apiUrl}/remote-hosts/${id}/test_connection/`, {});
|
|
1001
1001
|
}
|
|
1002
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1003
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1002
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ApiService, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1003
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ApiService, providedIn: 'root' });
|
|
1004
1004
|
}
|
|
1005
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1005
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ApiService, decorators: [{
|
|
1006
1006
|
type: Injectable,
|
|
1007
1007
|
args: [{
|
|
1008
1008
|
providedIn: 'root'
|
|
@@ -1105,10 +1105,10 @@ class BaseApiService {
|
|
|
1105
1105
|
}
|
|
1106
1106
|
return httpParams;
|
|
1107
1107
|
}
|
|
1108
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1109
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1108
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: BaseApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1109
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: BaseApiService, providedIn: 'root' });
|
|
1110
1110
|
}
|
|
1111
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1111
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: BaseApiService, decorators: [{
|
|
1112
1112
|
type: Injectable,
|
|
1113
1113
|
args: [{
|
|
1114
1114
|
providedIn: 'root'
|
|
@@ -1155,10 +1155,10 @@ class ToastService {
|
|
|
1155
1155
|
generateId() {
|
|
1156
1156
|
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
1157
1157
|
}
|
|
1158
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1159
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1158
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1159
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, providedIn: 'root' });
|
|
1160
1160
|
}
|
|
1161
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1161
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, decorators: [{
|
|
1162
1162
|
type: Injectable,
|
|
1163
1163
|
args: [{
|
|
1164
1164
|
providedIn: 'root'
|
|
@@ -1228,10 +1228,10 @@ class NotificationService {
|
|
|
1228
1228
|
this.toastService.info(`${title}: ${message}`);
|
|
1229
1229
|
}
|
|
1230
1230
|
}
|
|
1231
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1232
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1231
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, deps: [{ token: ToastService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1232
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, providedIn: 'root' });
|
|
1233
1233
|
}
|
|
1234
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1234
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, decorators: [{
|
|
1235
1235
|
type: Injectable,
|
|
1236
1236
|
args: [{
|
|
1237
1237
|
providedIn: 'root'
|
|
@@ -1255,6 +1255,18 @@ class SiteConfigService extends BaseApiService {
|
|
|
1255
1255
|
constructor() {
|
|
1256
1256
|
super();
|
|
1257
1257
|
this.loadConfig();
|
|
1258
|
+
this.startPeriodicRefresh();
|
|
1259
|
+
}
|
|
1260
|
+
startPeriodicRefresh() {
|
|
1261
|
+
interval(60000).subscribe(() => {
|
|
1262
|
+
this.fetchConfigFromBackend().subscribe({
|
|
1263
|
+
next: (config) => {
|
|
1264
|
+
this.configSubject.next({ ...this.defaultConfig, ...config });
|
|
1265
|
+
localStorage.setItem('site_config', JSON.stringify(config));
|
|
1266
|
+
},
|
|
1267
|
+
error: () => { }
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1258
1270
|
}
|
|
1259
1271
|
loadConfig() {
|
|
1260
1272
|
const savedConfig = localStorage.getItem('site_config');
|
|
@@ -1284,10 +1296,16 @@ class SiteConfigService extends BaseApiService {
|
|
|
1284
1296
|
return this.get(`${this.apiUrl}/site-config/public/`);
|
|
1285
1297
|
}
|
|
1286
1298
|
getCurrentConfig() {
|
|
1287
|
-
return this.get(`${this.apiUrl}/site-config/current/`)
|
|
1299
|
+
return this.get(`${this.apiUrl}/site-config/current/`).pipe(tap$1(config => {
|
|
1300
|
+
this.configSubject.next({ ...this.defaultConfig, ...config });
|
|
1301
|
+
localStorage.setItem('site_config', JSON.stringify(config));
|
|
1302
|
+
}));
|
|
1288
1303
|
}
|
|
1289
1304
|
updateConfig(config) {
|
|
1290
|
-
return this.put(`${this.apiUrl}/site-config/update_config/`, config)
|
|
1305
|
+
return this.put(`${this.apiUrl}/site-config/update_config/`, config).pipe(tap$1(updatedConfig => {
|
|
1306
|
+
this.configSubject.next({ ...this.defaultConfig, ...updatedConfig });
|
|
1307
|
+
localStorage.setItem('site_config', JSON.stringify(updatedConfig));
|
|
1308
|
+
}));
|
|
1291
1309
|
}
|
|
1292
1310
|
getSiteName() {
|
|
1293
1311
|
return this.configSubject.value.siteName;
|
|
@@ -1308,10 +1326,10 @@ class SiteConfigService extends BaseApiService {
|
|
|
1308
1326
|
isOrcidLoginEnabled() {
|
|
1309
1327
|
return this.configSubject.value.enableOrcidLogin === true;
|
|
1310
1328
|
}
|
|
1311
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1312
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1329
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SiteConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1330
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SiteConfigService, providedIn: 'root' });
|
|
1313
1331
|
}
|
|
1314
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1332
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SiteConfigService, decorators: [{
|
|
1315
1333
|
type: Injectable,
|
|
1316
1334
|
args: [{
|
|
1317
1335
|
providedIn: 'root'
|
|
@@ -1382,10 +1400,10 @@ class ThemeService {
|
|
|
1382
1400
|
default: return 'Auto Mode';
|
|
1383
1401
|
}
|
|
1384
1402
|
}
|
|
1385
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1386
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1403
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1404
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
1387
1405
|
}
|
|
1388
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1406
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, decorators: [{
|
|
1389
1407
|
type: Injectable,
|
|
1390
1408
|
args: [{
|
|
1391
1409
|
providedIn: 'root'
|
|
@@ -1459,10 +1477,10 @@ class UserManagementService {
|
|
|
1459
1477
|
getCurrentTotalUsers() {
|
|
1460
1478
|
return this.totalUsersSubject.getValue();
|
|
1461
1479
|
}
|
|
1462
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1463
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1480
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1481
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, providedIn: 'root' });
|
|
1464
1482
|
}
|
|
1465
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1483
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, decorators: [{
|
|
1466
1484
|
type: Injectable,
|
|
1467
1485
|
args: [{
|
|
1468
1486
|
providedIn: 'root'
|
|
@@ -1762,10 +1780,10 @@ class WebSocketService {
|
|
|
1762
1780
|
}
|
|
1763
1781
|
});
|
|
1764
1782
|
}
|
|
1765
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1766
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1783
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1784
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, providedIn: 'root' });
|
|
1767
1785
|
}
|
|
1768
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1786
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1769
1787
|
type: Injectable,
|
|
1770
1788
|
args: [{
|
|
1771
1789
|
providedIn: 'root'
|
|
@@ -1847,10 +1865,10 @@ class WebSocketConfigService {
|
|
|
1847
1865
|
const notificationEndpoint = endpoints.find(e => e.endpoint.includes('notifications'));
|
|
1848
1866
|
return notificationEndpoint?.endpoint || endpoints[0].endpoint;
|
|
1849
1867
|
}
|
|
1850
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1851
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1868
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1869
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketConfigService, providedIn: 'root' });
|
|
1852
1870
|
}
|
|
1853
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1871
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketConfigService, decorators: [{
|
|
1854
1872
|
type: Injectable,
|
|
1855
1873
|
args: [{
|
|
1856
1874
|
providedIn: 'root'
|
|
@@ -1941,10 +1959,10 @@ class LabGroupService extends BaseApiService {
|
|
|
1941
1959
|
getLabGroupPermissionsForUser(userId) {
|
|
1942
1960
|
return this.getLabGroupPermissions({ user: userId });
|
|
1943
1961
|
}
|
|
1944
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1945
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
1962
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LabGroupService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1963
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LabGroupService, providedIn: 'root' });
|
|
1946
1964
|
}
|
|
1947
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1965
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LabGroupService, decorators: [{
|
|
1948
1966
|
type: Injectable,
|
|
1949
1967
|
args: [{
|
|
1950
1968
|
providedIn: 'root'
|
|
@@ -2084,10 +2102,32 @@ class LoginComponent {
|
|
|
2084
2102
|
});
|
|
2085
2103
|
this.authService.isAuthenticated$.subscribe(isAuthenticated => {
|
|
2086
2104
|
if (isAuthenticated) {
|
|
2087
|
-
this.
|
|
2105
|
+
this.navigateToReturnUrl();
|
|
2088
2106
|
}
|
|
2089
2107
|
});
|
|
2090
2108
|
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Navigate to return URL, properly handling query parameters
|
|
2111
|
+
*/
|
|
2112
|
+
navigateToReturnUrl() {
|
|
2113
|
+
const url = this.returnUrl;
|
|
2114
|
+
if (!url || url === '/') {
|
|
2115
|
+
this.router.navigate(['/']);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
const [path, queryString] = url.split('?');
|
|
2119
|
+
if (queryString) {
|
|
2120
|
+
const queryParams = {};
|
|
2121
|
+
const params = new URLSearchParams(queryString);
|
|
2122
|
+
params.forEach((value, key) => {
|
|
2123
|
+
queryParams[key] = value;
|
|
2124
|
+
});
|
|
2125
|
+
this.router.navigate([path], { queryParams });
|
|
2126
|
+
}
|
|
2127
|
+
else {
|
|
2128
|
+
this.router.navigate([path]);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2091
2131
|
/**
|
|
2092
2132
|
* Clean return URL to prevent accumulating login URLs
|
|
2093
2133
|
*/
|
|
@@ -2155,7 +2195,7 @@ class LoginComponent {
|
|
|
2155
2195
|
next: (response) => {
|
|
2156
2196
|
this.success.set('Login successful!');
|
|
2157
2197
|
setTimeout(() => {
|
|
2158
|
-
this.
|
|
2198
|
+
this.navigateToReturnUrl();
|
|
2159
2199
|
}, 1000);
|
|
2160
2200
|
},
|
|
2161
2201
|
error: (error) => {
|
|
@@ -2199,7 +2239,7 @@ class LoginComponent {
|
|
|
2199
2239
|
next: (response) => {
|
|
2200
2240
|
this.success.set(`Welcome, ${response.user.firstName || response.user.username}!`);
|
|
2201
2241
|
setTimeout(() => {
|
|
2202
|
-
this.
|
|
2242
|
+
this.navigateToReturnUrl();
|
|
2203
2243
|
}, 1000);
|
|
2204
2244
|
},
|
|
2205
2245
|
error: (error) => {
|
|
@@ -2235,10 +2275,10 @@ class LoginComponent {
|
|
|
2235
2275
|
queryParams: { returnUrl: this.returnUrl }
|
|
2236
2276
|
});
|
|
2237
2277
|
}
|
|
2238
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2239
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
2278
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2279
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: LoginComponent, isStandalone: true, selector: "ccc-login", ngImport: i0, template: "<div class=\"container\">\r\n <div class=\"login-wrapper\">\r\n <div class=\"card\">\r\n <div class=\"card-body\">\r\n @if (siteConfig$ | async; as config) {\r\n <h3 class=\"card-title text-center mb-4\">\r\n <i class=\"bi bi-flask me-2\"></i>{{ config.siteName }}\r\n </h3>\r\n }\r\n\r\n <!-- Success Alert -->\r\n @if (success()) {\r\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\r\n {{ success() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- Error Alert -->\r\n @if (error()) {\r\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\r\n {{ error() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- ORCID Login Section -->\r\n @if (shouldShowOrcidLogin()) {\r\n <div class=\"mb-4\">\r\n <button \r\n type=\"button\" \r\n class=\"btn btn-primary w-100 mb-3\"\r\n [disabled]=\"loading()\"\r\n (click)=\"loginWithORCID()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\r\n }\r\n <i class=\"bi bi-person-badge me-2\"></i>\r\n Login with ORCID\r\n </button>\r\n <p class=\"text-muted text-center small\">\r\n Sign in with your ORCID account for seamless access\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- Divider -->\r\n @if (shouldShowOrcidLogin() && shouldShowRegularLogin()) {\r\n <div class=\"text-center mb-4\">\r\n <span class=\"text-muted\">or</span>\r\n </div>\r\n }\r\n\r\n <!-- Traditional Login Form -->\r\n @if (shouldShowRegularLogin()) {\r\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"mb-3\">\r\n <label for=\"username\" class=\"form-label\">Username</label>\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-person\"></i>\r\n </span>\r\n <input \r\n type=\"text\" \r\n class=\"form-control\" \r\n id=\"username\"\r\n formControlName=\"username\"\r\n [class.is-invalid]=\"loginForm.get('username')?.invalid && loginForm.get('username')?.touched\"\r\n placeholder=\"Enter your username\">\r\n </div>\r\n @if (loginForm.get('username')?.invalid && loginForm.get('username')?.touched) {\r\n <div class=\"invalid-feedback d-block\">\r\n Username is required\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"mb-3\">\r\n <label for=\"password\" class=\"form-label\">Password</label>\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-lock\"></i>\r\n </span>\r\n <input \r\n type=\"password\" \r\n class=\"form-control\" \r\n id=\"password\"\r\n formControlName=\"password\"\r\n [class.is-invalid]=\"loginForm.get('password')?.invalid && loginForm.get('password')?.touched\"\r\n placeholder=\"Enter your password\">\r\n </div>\r\n @if (loginForm.get('password')?.invalid && loginForm.get('password')?.touched) {\r\n <div class=\"invalid-feedback d-block\">\r\n Password is required\r\n </div>\r\n }\r\n </div>\r\n\r\n <button \r\n type=\"submit\" \r\n class=\"btn btn-outline-primary w-100\"\r\n [disabled]=\"loginForm.invalid || loading()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\r\n }\r\n <i class=\"bi bi-box-arrow-in-right me-2\"></i>\r\n Sign In\r\n </button>\r\n </form>\r\n }\r\n\r\n <!-- Registration Information -->\r\n @if (shouldShowRegistration()) {\r\n <div class=\"mt-4 text-center\">\r\n <div class=\"alert alert-info py-2\" role=\"alert\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n <strong>New Users:</strong> {{ registrationMessage() }}\r\n <div class=\"mt-2\">\r\n <button type=\"button\" class=\"btn btn-outline-primary btn-sm\" (click)=\"goToRegister()\">\r\n <i class=\"bi bi-person-plus me-1\"></i>Create Account\r\n </button>\r\n </div>\r\n <div class=\"mt-1\">\r\n <small class=\"text-muted\">\r\n @if (shouldShowOrcidLogin()) {\r\n <span>You can also sign in with ORCID to create an account</span>\r\n }\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Additional Information -->\r\n @if (siteConfig$ | async; as config) {\r\n <div class=\"mt-4 text-center\">\r\n <p class=\"text-muted small\">\r\n {{ config.siteName }} - Scientific Metadata Management\r\n </p>\r\n @if (shouldShowOrcidLogin() && !shouldShowRegistration()) {\r\n <p class=\"text-muted small\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n New users can sign in directly with ORCID\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".container{min-height:100vh;display:flex;align-items:center;justify-content:center;background:none;overflow:auto}.login-wrapper{width:100%;max-width:450px}.card{border:none;border-radius:12px;box-shadow:0 8px 24px var(--cupcake-shadow);background:var(--cupcake-card-bg);backdrop-filter:blur(10px);width:100%}.card-body{padding:2rem}.card-title{color:var(--cupcake-text);font-weight:600}.btn-primary{background:linear-gradient(135deg,var(--cupcake-primary) 0%,var(--cupcake-primary-dark) 100%);border:none;border-radius:8px;padding:12px;font-weight:500;transition:all .3s ease}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(var(--cupcake-primary-rgb),.3)}.btn-outline-primary{border-radius:8px;padding:12px;font-weight:500;transition:all .3s ease}.btn-outline-primary:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(var(--cupcake-primary-rgb),.2)}.input-group-text{background:var(--cupcake-bg-tertiary);border-color:var(--cupcake-border);color:var(--cupcake-text-muted)}.form-control{border-radius:0 8px 8px 0;transition:all .3s ease}.form-control:focus{box-shadow:0 0 0 .2rem rgba(var(--cupcake-primary-rgb),.15);border-color:var(--cupcake-primary)}.input-group-text:first-child{border-radius:8px 0 0 8px}.spinner-border-sm{width:1rem;height:1rem}ngb-alert{border-radius:8px;border:none}ngb-alert.alert-success{background:var(--bs-success-bg-subtle, rgba(25, 135, 84, .1));color:var(--bs-success-text-emphasis, #0f5132);border:1px solid var(--bs-success-border-subtle, rgba(25, 135, 84, .2))}ngb-alert.alert-danger{background:var(--bs-danger-bg-subtle, rgba(220, 53, 69, .1));color:var(--bs-danger-text-emphasis, #842029);border:1px solid var(--bs-danger-border-subtle, rgba(220, 53, 69, .2))}:root[data-bs-theme=dark] ngb-alert.alert-success,.dark-mode ngb-alert.alert-success{background:#19875426;color:#75b798;border:1px solid rgba(25,135,84,.3)}:root[data-bs-theme=dark] ngb-alert.alert-danger,.dark-mode ngb-alert.alert-danger{background:#dc354526;color:#ea868f;border:1px solid rgba(220,53,69,.3)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: NgbAlert, selector: "ngb-alert", inputs: ["animation", "dismissible", "type"], outputs: ["closed"], exportAs: ["ngbAlert"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] });
|
|
2240
2280
|
}
|
|
2241
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2281
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LoginComponent, decorators: [{
|
|
2242
2282
|
type: Component,
|
|
2243
2283
|
args: [{ selector: 'ccc-login', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbAlert], template: "<div class=\"container\">\r\n <div class=\"login-wrapper\">\r\n <div class=\"card\">\r\n <div class=\"card-body\">\r\n @if (siteConfig$ | async; as config) {\r\n <h3 class=\"card-title text-center mb-4\">\r\n <i class=\"bi bi-flask me-2\"></i>{{ config.siteName }}\r\n </h3>\r\n }\r\n\r\n <!-- Success Alert -->\r\n @if (success()) {\r\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\r\n {{ success() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- Error Alert -->\r\n @if (error()) {\r\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\r\n {{ error() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- ORCID Login Section -->\r\n @if (shouldShowOrcidLogin()) {\r\n <div class=\"mb-4\">\r\n <button \r\n type=\"button\" \r\n class=\"btn btn-primary w-100 mb-3\"\r\n [disabled]=\"loading()\"\r\n (click)=\"loginWithORCID()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\r\n }\r\n <i class=\"bi bi-person-badge me-2\"></i>\r\n Login with ORCID\r\n </button>\r\n <p class=\"text-muted text-center small\">\r\n Sign in with your ORCID account for seamless access\r\n </p>\r\n </div>\r\n }\r\n\r\n <!-- Divider -->\r\n @if (shouldShowOrcidLogin() && shouldShowRegularLogin()) {\r\n <div class=\"text-center mb-4\">\r\n <span class=\"text-muted\">or</span>\r\n </div>\r\n }\r\n\r\n <!-- Traditional Login Form -->\r\n @if (shouldShowRegularLogin()) {\r\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"mb-3\">\r\n <label for=\"username\" class=\"form-label\">Username</label>\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-person\"></i>\r\n </span>\r\n <input \r\n type=\"text\" \r\n class=\"form-control\" \r\n id=\"username\"\r\n formControlName=\"username\"\r\n [class.is-invalid]=\"loginForm.get('username')?.invalid && loginForm.get('username')?.touched\"\r\n placeholder=\"Enter your username\">\r\n </div>\r\n @if (loginForm.get('username')?.invalid && loginForm.get('username')?.touched) {\r\n <div class=\"invalid-feedback d-block\">\r\n Username is required\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"mb-3\">\r\n <label for=\"password\" class=\"form-label\">Password</label>\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-lock\"></i>\r\n </span>\r\n <input \r\n type=\"password\" \r\n class=\"form-control\" \r\n id=\"password\"\r\n formControlName=\"password\"\r\n [class.is-invalid]=\"loginForm.get('password')?.invalid && loginForm.get('password')?.touched\"\r\n placeholder=\"Enter your password\">\r\n </div>\r\n @if (loginForm.get('password')?.invalid && loginForm.get('password')?.touched) {\r\n <div class=\"invalid-feedback d-block\">\r\n Password is required\r\n </div>\r\n }\r\n </div>\r\n\r\n <button \r\n type=\"submit\" \r\n class=\"btn btn-outline-primary w-100\"\r\n [disabled]=\"loginForm.invalid || loading()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\r\n }\r\n <i class=\"bi bi-box-arrow-in-right me-2\"></i>\r\n Sign In\r\n </button>\r\n </form>\r\n }\r\n\r\n <!-- Registration Information -->\r\n @if (shouldShowRegistration()) {\r\n <div class=\"mt-4 text-center\">\r\n <div class=\"alert alert-info py-2\" role=\"alert\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n <strong>New Users:</strong> {{ registrationMessage() }}\r\n <div class=\"mt-2\">\r\n <button type=\"button\" class=\"btn btn-outline-primary btn-sm\" (click)=\"goToRegister()\">\r\n <i class=\"bi bi-person-plus me-1\"></i>Create Account\r\n </button>\r\n </div>\r\n <div class=\"mt-1\">\r\n <small class=\"text-muted\">\r\n @if (shouldShowOrcidLogin()) {\r\n <span>You can also sign in with ORCID to create an account</span>\r\n }\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Additional Information -->\r\n @if (siteConfig$ | async; as config) {\r\n <div class=\"mt-4 text-center\">\r\n <p class=\"text-muted small\">\r\n {{ config.siteName }} - Scientific Metadata Management\r\n </p>\r\n @if (shouldShowOrcidLogin() && !shouldShowRegistration()) {\r\n <p class=\"text-muted small\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n New users can sign in directly with ORCID\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".container{min-height:100vh;display:flex;align-items:center;justify-content:center;background:none;overflow:auto}.login-wrapper{width:100%;max-width:450px}.card{border:none;border-radius:12px;box-shadow:0 8px 24px var(--cupcake-shadow);background:var(--cupcake-card-bg);backdrop-filter:blur(10px);width:100%}.card-body{padding:2rem}.card-title{color:var(--cupcake-text);font-weight:600}.btn-primary{background:linear-gradient(135deg,var(--cupcake-primary) 0%,var(--cupcake-primary-dark) 100%);border:none;border-radius:8px;padding:12px;font-weight:500;transition:all .3s ease}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(var(--cupcake-primary-rgb),.3)}.btn-outline-primary{border-radius:8px;padding:12px;font-weight:500;transition:all .3s ease}.btn-outline-primary:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(var(--cupcake-primary-rgb),.2)}.input-group-text{background:var(--cupcake-bg-tertiary);border-color:var(--cupcake-border);color:var(--cupcake-text-muted)}.form-control{border-radius:0 8px 8px 0;transition:all .3s ease}.form-control:focus{box-shadow:0 0 0 .2rem rgba(var(--cupcake-primary-rgb),.15);border-color:var(--cupcake-primary)}.input-group-text:first-child{border-radius:8px 0 0 8px}.spinner-border-sm{width:1rem;height:1rem}ngb-alert{border-radius:8px;border:none}ngb-alert.alert-success{background:var(--bs-success-bg-subtle, rgba(25, 135, 84, .1));color:var(--bs-success-text-emphasis, #0f5132);border:1px solid var(--bs-success-border-subtle, rgba(25, 135, 84, .2))}ngb-alert.alert-danger{background:var(--bs-danger-bg-subtle, rgba(220, 53, 69, .1));color:var(--bs-danger-text-emphasis, #842029);border:1px solid var(--bs-danger-border-subtle, rgba(220, 53, 69, .2))}:root[data-bs-theme=dark] ngb-alert.alert-success,.dark-mode ngb-alert.alert-success{background:#19875426;color:#75b798;border:1px solid rgba(25,135,84,.3)}:root[data-bs-theme=dark] ngb-alert.alert-danger,.dark-mode ngb-alert.alert-danger{background:#dc354526;color:#ea868f;border:1px solid rgba(220,53,69,.3)}\n"] }]
|
|
2244
2284
|
}], ctorParameters: () => [] });
|
|
@@ -2429,10 +2469,10 @@ class RegisterComponent {
|
|
|
2429
2469
|
canSubmitForm = computed(() => {
|
|
2430
2470
|
return this.registrationForm.valid && this.registrationEnabled() && !this.loading();
|
|
2431
2471
|
}, ...(ngDevMode ? [{ debugName: "canSubmitForm" }] : []));
|
|
2432
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2433
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: RegisterComponent, isStandalone: true, selector: "ccc-register", ngImport: i0, template: "<div class=\"container\">\n <div class=\"register-wrapper\">\n <div class=\"card\">\n <div class=\"card-body\">\n @if (siteConfig$ | async; as config) {\n <h3 class=\"card-title text-center mb-4\">\n <i class=\"bi bi-person-plus me-2\"></i>Create Account - {{ config.siteName }}\n </h3>\n }\n\n <!-- Success Alert -->\n @if (success()) {\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\n </ngb-alert>\n }\n\n <!-- Error Alert -->\n @if (error()) {\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <span [innerHTML]=\"error()?.replace('\\n', '<br>') || ''\"></span>\n </ngb-alert>\n }\n\n <!-- Registration Disabled Message -->\n @if (!registrationEnabled() && !loading()) {\n <div class=\"alert alert-warning text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Registration Unavailable</strong>\n <div class=\"mt-2\">\n {{ registrationStatus()?.message || 'Registration is currently disabled' }}\n </div>\n <div class=\"mt-3\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </div>\n }\n\n <!-- Registration Form -->\n @if (registrationEnabled()) {\n <form [formGroup]=\"registrationForm\" (ngSubmit)=\"onSubmit()\">\n <!-- Username -->\n <div class=\"mb-3\">\n <label for=\"username\" class=\"form-label\">Username <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-person\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"username\"\n formControlName=\"username\"\n [class.is-invalid]=\"hasFieldError('username')\"\n placeholder=\"Choose a username\">\n </div>\n @if (getFieldErrorMessage('username')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('username') }}\n </div>\n }\n <div class=\"form-text\">\n Username must be 3-150 characters. Only letters, numbers, and @/./+/-/_ allowed.\n </div>\n </div>\n\n <!-- Email -->\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">Email Address <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-envelope\"></i>\n </span>\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"email\"\n formControlName=\"email\"\n [class.is-invalid]=\"hasFieldError('email')\"\n placeholder=\"Enter your email address\">\n </div>\n @if (getFieldErrorMessage('email')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('email') }}\n </div>\n }\n </div>\n\n <!-- First Name and Last Name -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"first_name\" class=\"form-label\">First Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"first_name\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"hasFieldError('firstName')\"\n placeholder=\"First name\">\n @if (getFieldErrorMessage('firstName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('firstName') }}\n </div>\n }\n </div>\n <div class=\"col-md-6 mb-3\">\n <label for=\"last_name\" class=\"form-label\">Last Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"last_name\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"hasFieldError('lastName')\"\n placeholder=\"Last name\">\n @if (getFieldErrorMessage('lastName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('lastName') }}\n </div>\n }\n </div>\n </div>\n\n <!-- Password -->\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"password\"\n formControlName=\"password\"\n [class.is-invalid]=\"hasFieldError('password')\"\n placeholder=\"Create a password\">\n </div>\n @if (getFieldErrorMessage('password')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('password') }}\n </div>\n }\n <div class=\"form-text\">\n Password must be at least 8 characters long.\n </div>\n </div>\n\n <!-- Confirm Password -->\n <div class=\"mb-4\">\n <label for=\"confirm_password\" class=\"form-label\">Confirm Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock-fill\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"confirm_password\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"hasFieldError('confirmPassword')\"\n placeholder=\"Confirm your password\">\n </div>\n @if (getFieldErrorMessage('confirmPassword')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('confirmPassword') }}\n </div>\n }\n </div>\n\n <!-- Submit Button -->\n <div class=\"d-grid gap-2 mb-3\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"registrationForm.invalid || loading() || !registrationEnabled()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\n }\n @if (!loading()) {\n <i class=\"bi bi-person-plus me-2\"></i>\n }\n Create Account\n </button>\n </div>\n\n <!-- Back to Login -->\n <div class=\"text-center\">\n <p class=\"text-muted small mb-2\">Already have an account?</p>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </form>\n }\n\n <!-- Additional Information -->\n @if (siteConfig$ | async; as config) {\n <div class=\"mt-4 text-center\">\n <p class=\"text-muted small\">\n {{ config.siteName }} - Scientific Metadata Management\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem 1rem}.register-wrapper{width:100%;max-width:500px}.card{border:none;border-radius:1rem;box-shadow:0 .5rem 1rem #00000026}.card-body{padding:3rem}.card-title{font-size:1.5rem;font-weight:600;color:var(--bs-primary)}.form-control{border-radius:.5rem;border:1px solid var(--bs-border-color);padding:.75rem 1rem}.form-control:focus{border-color:var(--bs-primary);box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.input-group-text{border-radius:.5rem 0 0 .5rem;background:var(--bs-light);border:1px solid var(--bs-border-color)}.input-group-text i{color:var(--bs-secondary)}.input-group .form-control{border-radius:0 .5rem .5rem 0}.btn{border-radius:.5rem;padding:.75rem 1.5rem;font-weight:500;transition:all .2s ease-in-out}.btn-primary{background:linear-gradient(135deg,var(--bs-primary) 0%,color-mix(in srgb,var(--bs-primary) 80%,black) 100%);border:none}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 .25rem .5rem rgba(var(--bs-primary-rgb),.3)}.btn-primary:disabled{transform:none;box-shadow:none}.alert{border-radius:.75rem;border:none}.alert.alert-success{background:rgba(var(--bs-success-rgb),.1);color:var(--bs-success);border-left:4px solid var(--bs-success)}.alert.alert-danger{background:rgba(var(--bs-danger-rgb),.1);color:var(--bs-danger);border-left:4px solid var(--bs-danger)}.alert.alert-warning{background:rgba(var(--bs-warning-rgb),.1);color:var(--bs-warning-emphasis);border-left:4px solid var(--bs-warning)}.invalid-feedback{font-size:.875rem;color:var(--bs-danger)}.form-text{font-size:.8rem;color:var(--bs-secondary)}@media (max-width: 576px){.card-body{padding:2rem 1.5rem}.card-title{font-size:1.25rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: NgbAlert, selector: "ngb-alert", inputs: ["animation", "dismissible", "type"], outputs: ["closed"], exportAs: ["ngbAlert"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] });
|
|
2472
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: RegisterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2473
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: RegisterComponent, isStandalone: true, selector: "ccc-register", ngImport: i0, template: "<div class=\"container\">\n <div class=\"register-wrapper\">\n <div class=\"card\">\n <div class=\"card-body\">\n @if (siteConfig$ | async; as config) {\n <h3 class=\"card-title text-center mb-4\">\n <i class=\"bi bi-person-plus me-2\"></i>Create Account - {{ config.siteName }}\n </h3>\n }\n\n <!-- Success Alert -->\n @if (success()) {\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\n </ngb-alert>\n }\n\n <!-- Error Alert -->\n @if (error()) {\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <span [innerHTML]=\"error()?.replace('\\n', '<br>') || ''\"></span>\n </ngb-alert>\n }\n\n <!-- Registration Disabled Message -->\n @if (!registrationEnabled() && !loading()) {\n <div class=\"alert alert-warning text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Registration Unavailable</strong>\n <div class=\"mt-2\">\n {{ registrationStatus()?.message || 'Registration is currently disabled' }}\n </div>\n <div class=\"mt-3\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </div>\n }\n\n <!-- Registration Form -->\n @if (registrationEnabled()) {\n <form [formGroup]=\"registrationForm\" (ngSubmit)=\"onSubmit()\">\n <!-- Username -->\n <div class=\"mb-3\">\n <label for=\"username\" class=\"form-label\">Username <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-person\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"username\"\n formControlName=\"username\"\n [class.is-invalid]=\"hasFieldError('username')\"\n placeholder=\"Choose a username\">\n </div>\n @if (getFieldErrorMessage('username')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('username') }}\n </div>\n }\n <div class=\"form-text\">\n Username must be 3-150 characters. Only letters, numbers, and @/./+/-/_ allowed.\n </div>\n </div>\n\n <!-- Email -->\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">Email Address <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-envelope\"></i>\n </span>\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"email\"\n formControlName=\"email\"\n [class.is-invalid]=\"hasFieldError('email')\"\n placeholder=\"Enter your email address\">\n </div>\n @if (getFieldErrorMessage('email')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('email') }}\n </div>\n }\n </div>\n\n <!-- First Name and Last Name -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"first_name\" class=\"form-label\">First Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"first_name\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"hasFieldError('firstName')\"\n placeholder=\"First name\">\n @if (getFieldErrorMessage('firstName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('firstName') }}\n </div>\n }\n </div>\n <div class=\"col-md-6 mb-3\">\n <label for=\"last_name\" class=\"form-label\">Last Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"last_name\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"hasFieldError('lastName')\"\n placeholder=\"Last name\">\n @if (getFieldErrorMessage('lastName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('lastName') }}\n </div>\n }\n </div>\n </div>\n\n <!-- Password -->\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"password\"\n formControlName=\"password\"\n [class.is-invalid]=\"hasFieldError('password')\"\n placeholder=\"Create a password\">\n </div>\n @if (getFieldErrorMessage('password')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('password') }}\n </div>\n }\n <div class=\"form-text\">\n Password must be at least 8 characters long.\n </div>\n </div>\n\n <!-- Confirm Password -->\n <div class=\"mb-4\">\n <label for=\"confirm_password\" class=\"form-label\">Confirm Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock-fill\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"confirm_password\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"hasFieldError('confirmPassword')\"\n placeholder=\"Confirm your password\">\n </div>\n @if (getFieldErrorMessage('confirmPassword')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('confirmPassword') }}\n </div>\n }\n </div>\n\n <!-- Submit Button -->\n <div class=\"d-grid gap-2 mb-3\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"registrationForm.invalid || loading() || !registrationEnabled()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\n }\n @if (!loading()) {\n <i class=\"bi bi-person-plus me-2\"></i>\n }\n Create Account\n </button>\n </div>\n\n <!-- Back to Login -->\n <div class=\"text-center\">\n <p class=\"text-muted small mb-2\">Already have an account?</p>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </form>\n }\n\n <!-- Additional Information -->\n @if (siteConfig$ | async; as config) {\n <div class=\"mt-4 text-center\">\n <p class=\"text-muted small\">\n {{ config.siteName }} - Scientific Metadata Management\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem 1rem}.register-wrapper{width:100%;max-width:500px}.card{border:none;border-radius:1rem;box-shadow:0 .5rem 1rem #00000026}.card-body{padding:3rem}.card-title{font-size:1.5rem;font-weight:600;color:var(--bs-primary)}.form-control{border-radius:.5rem;border:1px solid var(--bs-border-color);padding:.75rem 1rem}.form-control:focus{border-color:var(--bs-primary);box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.input-group-text{border-radius:.5rem 0 0 .5rem;background:var(--bs-light);border:1px solid var(--bs-border-color)}.input-group-text i{color:var(--bs-secondary)}.input-group .form-control{border-radius:0 .5rem .5rem 0}.btn{border-radius:.5rem;padding:.75rem 1.5rem;font-weight:500;transition:all .2s ease-in-out}.btn-primary{background:linear-gradient(135deg,var(--bs-primary) 0%,color-mix(in srgb,var(--bs-primary) 80%,black) 100%);border:none}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 .25rem .5rem rgba(var(--bs-primary-rgb),.3)}.btn-primary:disabled{transform:none;box-shadow:none}.alert{border-radius:.75rem;border:none}.alert.alert-success{background:rgba(var(--bs-success-rgb),.1);color:var(--bs-success);border-left:4px solid var(--bs-success)}.alert.alert-danger{background:rgba(var(--bs-danger-rgb),.1);color:var(--bs-danger);border-left:4px solid var(--bs-danger)}.alert.alert-warning{background:rgba(var(--bs-warning-rgb),.1);color:var(--bs-warning-emphasis);border-left:4px solid var(--bs-warning)}.invalid-feedback{font-size:.875rem;color:var(--bs-danger)}.form-text{font-size:.8rem;color:var(--bs-secondary)}@media (max-width: 576px){.card-body{padding:2rem 1.5rem}.card-title{font-size:1.25rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: NgbAlert, selector: "ngb-alert", inputs: ["animation", "dismissible", "type"], outputs: ["closed"], exportAs: ["ngbAlert"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] });
|
|
2434
2474
|
}
|
|
2435
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2475
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: RegisterComponent, decorators: [{
|
|
2436
2476
|
type: Component,
|
|
2437
2477
|
args: [{ selector: 'ccc-register', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbAlert], template: "<div class=\"container\">\n <div class=\"register-wrapper\">\n <div class=\"card\">\n <div class=\"card-body\">\n @if (siteConfig$ | async; as config) {\n <h3 class=\"card-title text-center mb-4\">\n <i class=\"bi bi-person-plus me-2\"></i>Create Account - {{ config.siteName }}\n </h3>\n }\n\n <!-- Success Alert -->\n @if (success()) {\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\n </ngb-alert>\n }\n\n <!-- Error Alert -->\n @if (error()) {\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n <span [innerHTML]=\"error()?.replace('\\n', '<br>') || ''\"></span>\n </ngb-alert>\n }\n\n <!-- Registration Disabled Message -->\n @if (!registrationEnabled() && !loading()) {\n <div class=\"alert alert-warning text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Registration Unavailable</strong>\n <div class=\"mt-2\">\n {{ registrationStatus()?.message || 'Registration is currently disabled' }}\n </div>\n <div class=\"mt-3\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </div>\n }\n\n <!-- Registration Form -->\n @if (registrationEnabled()) {\n <form [formGroup]=\"registrationForm\" (ngSubmit)=\"onSubmit()\">\n <!-- Username -->\n <div class=\"mb-3\">\n <label for=\"username\" class=\"form-label\">Username <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-person\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"username\"\n formControlName=\"username\"\n [class.is-invalid]=\"hasFieldError('username')\"\n placeholder=\"Choose a username\">\n </div>\n @if (getFieldErrorMessage('username')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('username') }}\n </div>\n }\n <div class=\"form-text\">\n Username must be 3-150 characters. Only letters, numbers, and @/./+/-/_ allowed.\n </div>\n </div>\n\n <!-- Email -->\n <div class=\"mb-3\">\n <label for=\"email\" class=\"form-label\">Email Address <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-envelope\"></i>\n </span>\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"email\"\n formControlName=\"email\"\n [class.is-invalid]=\"hasFieldError('email')\"\n placeholder=\"Enter your email address\">\n </div>\n @if (getFieldErrorMessage('email')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('email') }}\n </div>\n }\n </div>\n\n <!-- First Name and Last Name -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"first_name\" class=\"form-label\">First Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"first_name\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"hasFieldError('firstName')\"\n placeholder=\"First name\">\n @if (getFieldErrorMessage('firstName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('firstName') }}\n </div>\n }\n </div>\n <div class=\"col-md-6 mb-3\">\n <label for=\"last_name\" class=\"form-label\">Last Name <span class=\"text-danger\">*</span></label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"last_name\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"hasFieldError('lastName')\"\n placeholder=\"Last name\">\n @if (getFieldErrorMessage('lastName')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('lastName') }}\n </div>\n }\n </div>\n </div>\n\n <!-- Password -->\n <div class=\"mb-3\">\n <label for=\"password\" class=\"form-label\">Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"password\"\n formControlName=\"password\"\n [class.is-invalid]=\"hasFieldError('password')\"\n placeholder=\"Create a password\">\n </div>\n @if (getFieldErrorMessage('password')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('password') }}\n </div>\n }\n <div class=\"form-text\">\n Password must be at least 8 characters long.\n </div>\n </div>\n\n <!-- Confirm Password -->\n <div class=\"mb-4\">\n <label for=\"confirm_password\" class=\"form-label\">Confirm Password <span class=\"text-danger\">*</span></label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-lock-fill\"></i>\n </span>\n <input \n type=\"password\" \n class=\"form-control\" \n id=\"confirm_password\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"hasFieldError('confirmPassword')\"\n placeholder=\"Confirm your password\">\n </div>\n @if (getFieldErrorMessage('confirmPassword')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldErrorMessage('confirmPassword') }}\n </div>\n }\n </div>\n\n <!-- Submit Button -->\n <div class=\"d-grid gap-2 mb-3\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"registrationForm.invalid || loading() || !registrationEnabled()\">\n @if (loading()) {\n <span class=\"spinner-border spinner-border-sm me-2\" aria-hidden=\"true\"></span>\n }\n @if (!loading()) {\n <i class=\"bi bi-person-plus me-2\"></i>\n }\n Create Account\n </button>\n </div>\n\n <!-- Back to Login -->\n <div class=\"text-center\">\n <p class=\"text-muted small mb-2\">Already have an account?</p>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"goToLogin()\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back to Login\n </button>\n </div>\n </form>\n }\n\n <!-- Additional Information -->\n @if (siteConfig$ | async; as config) {\n <div class=\"mt-4 text-center\">\n <p class=\"text-muted small\">\n {{ config.siteName }} - Scientific Metadata Management\n </p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem 1rem}.register-wrapper{width:100%;max-width:500px}.card{border:none;border-radius:1rem;box-shadow:0 .5rem 1rem #00000026}.card-body{padding:3rem}.card-title{font-size:1.5rem;font-weight:600;color:var(--bs-primary)}.form-control{border-radius:.5rem;border:1px solid var(--bs-border-color);padding:.75rem 1rem}.form-control:focus{border-color:var(--bs-primary);box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.input-group-text{border-radius:.5rem 0 0 .5rem;background:var(--bs-light);border:1px solid var(--bs-border-color)}.input-group-text i{color:var(--bs-secondary)}.input-group .form-control{border-radius:0 .5rem .5rem 0}.btn{border-radius:.5rem;padding:.75rem 1.5rem;font-weight:500;transition:all .2s ease-in-out}.btn-primary{background:linear-gradient(135deg,var(--bs-primary) 0%,color-mix(in srgb,var(--bs-primary) 80%,black) 100%);border:none}.btn-primary:hover{transform:translateY(-1px);box-shadow:0 .25rem .5rem rgba(var(--bs-primary-rgb),.3)}.btn-primary:disabled{transform:none;box-shadow:none}.alert{border-radius:.75rem;border:none}.alert.alert-success{background:rgba(var(--bs-success-rgb),.1);color:var(--bs-success);border-left:4px solid var(--bs-success)}.alert.alert-danger{background:rgba(var(--bs-danger-rgb),.1);color:var(--bs-danger);border-left:4px solid var(--bs-danger)}.alert.alert-warning{background:rgba(var(--bs-warning-rgb),.1);color:var(--bs-warning-emphasis);border-left:4px solid var(--bs-warning)}.invalid-feedback{font-size:.875rem;color:var(--bs-danger)}.form-text{font-size:.8rem;color:var(--bs-secondary)}@media (max-width: 576px){.card-body{padding:2rem 1.5rem}.card-title{font-size:1.25rem}}\n"] }]
|
|
2438
2478
|
}], ctorParameters: () => [] });
|
|
@@ -2649,10 +2689,10 @@ class UserManagementComponent {
|
|
|
2649
2689
|
getUserDisplayName(user) {
|
|
2650
2690
|
return this.userManagementService.getUserDisplayName(user);
|
|
2651
2691
|
}
|
|
2652
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2653
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: UserManagementComponent, isStandalone: true, selector: "ccc-user-management", ngImport: i0, template: "<div class=\"user-management-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-people-fill me-2 text-primary\"></i>User Management\n </h4>\n <span class=\"badge bg-primary ms-3\">{{ totalUsers() }} users</span>\n </div>\n\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n (click)=\"openCreateUserModal(createUserModal)\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"users-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <label for=\"search\" class=\"form-label\">Search</label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"search\"\n formControlName=\"search\"\n placeholder=\"Search by username, email, or name\">\n </div>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isStaff\" class=\"form-label\">Staff Status</label>\n <select class=\"form-select\" id=\"isStaff\" formControlName=\"isStaff\">\n <option value=\"\">All Users</option>\n <option value=\"true\">Staff Only</option>\n <option value=\"false\">Regular Users</option>\n </select>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isActive\" class=\"form-label\">Account Status</label>\n <select class=\"form-select\" id=\"isActive\" formControlName=\"isActive\">\n <option value=\"\">All Accounts</option>\n <option value=\"true\">Active Only</option>\n <option value=\"false\">Inactive Only</option>\n </select>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Success/Error Messages -->\n @if (successMessage()) {\n <div class=\"alert alert-success alert-dismissible\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ successMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n @if (errorMessage()) {\n <div class=\"alert alert-danger alert-dismissible\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n <!-- Users Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Users\n <span class=\"badge bg-primary ms-2\">{{ users().length }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasResults()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th>User</th>\n <th>Contact</th>\n <th>Status</th>\n <th>Joined</th>\n <th>Last Login</th>\n <th class=\"text-end\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (user of users(); track user.id) {\n <tr>\n <td>\n <div class=\"d-flex align-items-center\">\n <div>\n <div class=\"fw-semibold\">{{ getUserDisplayName(user) }}</div>\n <small class=\"text-muted\">@{{ user.username }}</small>\n </div>\n </div>\n </td>\n <td>\n <div>{{ user.email }}</div>\n </td>\n <td>\n <div class=\"d-flex gap-1 flex-wrap\">\n @if (user.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n @if (user.isStaff) {\n <span class=\"badge bg-warning\">Staff</span>\n }\n </div>\n </td>\n <td>{{ formatDate(user.dateJoined) }}</td>\n <td>{{ formatDate(user.lastLogin) }}</td>\n <td class=\"text-end\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditUserModal(editUserModal, user)\"\n title=\"Edit user\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning\"\n (click)=\"openPasswordResetModal(passwordResetModal, user)\"\n title=\"Reset password\">\n <i class=\"bi bi-shield-lock\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isActive ? 'btn-outline-secondary' : 'btn-outline-success'\"\n (click)=\"toggleUserStatus(user)\"\n [title]=\"user.isActive ? 'Deactivate user' : 'Activate user'\">\n <i class=\"bi\" [class]=\"user.isActive ? 'bi-person-dash' : 'bi-person-check'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isStaff ? 'btn-outline-secondary' : 'btn-outline-info'\"\n (click)=\"toggleStaffStatus(user)\"\n [title]=\"user.isStaff ? 'Remove staff privileges' : 'Grant staff privileges'\">\n <i class=\"bi\" [class]=\"user.isStaff ? 'bi-person-x' : 'bi-person-badge'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-danger\"\n (click)=\"deleteUser(user)\"\n title=\"Delete user\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"text-muted\">\n Showing {{ showingFrom() }} to {{ showingTo() }} of {{ totalUsers() }} users\n </div>\n <nav>\n <ul class=\"pagination pagination-sm mb-0\">\n <li class=\"page-item\" [class.disabled]=\"!canGoToPreviousPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() - 1)\" [disabled]=\"!canGoToPreviousPage()\">\n <i class=\"bi bi-chevron-left\"></i>\n </button>\n </li>\n @for (page of pages(); track page) {\n <li class=\"page-item\" [class.active]=\"page === currentPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(page)\">{{ page }}</button>\n </li>\n }\n <li class=\"page-item\" [class.disabled]=\"!canGoToNextPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() + 1)\" [disabled]=\"!canGoToNextPage()\">\n <i class=\"bi bi-chevron-right\"></i>\n </button>\n </li>\n </ul>\n </nav>\n </div>\n }\n } @else {\n <div class=\"text-center p-4\">\n <i class=\"bi bi-people display-4 text-muted\"></i>\n <p class=\"text-muted mt-2\">No users found</p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Create User Modal -->\n<ng-template #createUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-person-plus me-2\"></i>Create User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n <form #createForm=\"ngForm\" (ngSubmit)=\"createUser(createForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"createUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createUsername\"\n name=\"username\"\n ngModel\n required\n #usernameField=\"ngModel\">\n @if (usernameField.invalid && usernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"createEmail\"\n name=\"email\"\n ngModel\n required\n email\n #emailField=\"ngModel\">\n @if (emailField.invalid && emailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createFirstName\"\n name=\"firstName\"\n ngModel\n required\n #firstNameField=\"ngModel\">\n @if (firstNameField.invalid && firstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createLastName\"\n name=\"lastName\"\n ngModel\n required\n #lastNameField=\"ngModel\">\n @if (lastNameField.invalid && lastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createPassword\" class=\"form-label\">Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPassword\"\n name=\"password\"\n ngModel\n required\n minlength=\"8\"\n #passwordField=\"ngModel\">\n @if (passwordField.invalid && passwordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createPasswordConfirm\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPasswordConfirm\"\n name=\"password_confirm\"\n ngModel\n required\n #passwordConfirmField=\"ngModel\">\n @if (passwordConfirmField.invalid && passwordConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsStaff\"\n name=\"isStaff\"\n ngModel>\n <label class=\"form-check-label\" for=\"createIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsActive\"\n name=\"isActive\"\n ngModel\n checked>\n <label class=\"form-check-label\" for=\"createIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"createUser(createForm.value)\"\n [disabled]=\"createForm.invalid || !canCreateUser()\">\n @if (isCreatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n</ng-template>\n\n<!-- Edit User Modal -->\n<ng-template #editUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil me-2\"></i>Edit User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <form #editForm=\"ngForm\" id=\"editUserForm\" (ngSubmit)=\"updateUser(selectedUser()!.id!, editForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"editUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editUsername\"\n name=\"username\"\n [ngModel]=\"selectedUser()!.username\"\n required\n #editUsernameField=\"ngModel\">\n @if (editUsernameField.invalid && editUsernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"editEmail\"\n name=\"email\"\n [ngModel]=\"selectedUser()!.email\"\n required\n email\n #editEmailField=\"ngModel\">\n @if (editEmailField.invalid && editEmailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"editFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editFirstName\"\n name=\"firstName\"\n [ngModel]=\"selectedUser()!.firstName\"\n required\n #editFirstNameField=\"ngModel\">\n @if (editFirstNameField.invalid && editFirstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editLastName\"\n name=\"lastName\"\n [ngModel]=\"selectedUser()!.lastName\"\n required\n #editLastNameField=\"ngModel\">\n @if (editLastNameField.invalid && editLastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsStaff\"\n name=\"isStaff\"\n [ngModel]=\"selectedUser()!.isStaff\">\n <label class=\"form-check-label\" for=\"editIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsActive\"\n name=\"isActive\"\n [ngModel]=\"selectedUser()!.isActive\">\n <label class=\"form-check-label\" for=\"editIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n form=\"editUserForm\"\n [disabled]=\"!canUpdateUser()\">\n @if (isUpdatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>Update User\n </button>\n }\n </div>\n</ng-template>\n\n<!-- Password Reset Modal -->\n<ng-template #passwordResetModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-shield-lock me-2\"></i>Reset Password\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n You are about to reset the password for user <strong>{{ selectedUserDisplayName() }}</strong>.\n </div>\n \n <form #resetForm=\"ngForm\" id=\"resetPasswordForm\" (ngSubmit)=\"resetUserPassword(selectedUser()!.id!, resetForm.value)\">\n <div class=\"mb-3\">\n <label for=\"resetNewPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetNewPassword\"\n name=\"new_password\"\n ngModel\n required\n minlength=\"8\"\n #resetPasswordField=\"ngModel\">\n @if (resetPasswordField.invalid && resetPasswordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"resetConfirmPassword\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetConfirmPassword\"\n name=\"confirm_password\"\n ngModel\n required\n #resetConfirmField=\"ngModel\">\n @if (resetConfirmField.invalid && resetConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n <div class=\"mb-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"forcePasswordChange\"\n name=\"force_password_change\"\n ngModel>\n <label class=\"form-check-label\" for=\"forcePasswordChange\">\n Force user to change password on next login\n </label>\n </div>\n </div>\n <div class=\"mb-3\">\n <label for=\"resetReason\" class=\"form-label\">Reason (optional)</label>\n <textarea\n class=\"form-control\"\n id=\"resetReason\"\n name=\"reason\"\n ngModel\n rows=\"2\"\n placeholder=\"Optional reason for password reset\"></textarea>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-warning\"\n form=\"resetPasswordForm\"\n [disabled]=\"!canResetPassword()\">\n @if (isResettingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-lock me-1\"></i>Reset Password\n </button>\n }\n </div>\n</ng-template>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NgbModule }] });
|
|
2692
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2693
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: UserManagementComponent, isStandalone: true, selector: "ccc-user-management", ngImport: i0, template: "<div class=\"user-management-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-people-fill me-2 text-primary\"></i>User Management\n </h4>\n <span class=\"badge bg-primary ms-3\">{{ totalUsers() }} users</span>\n </div>\n\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n (click)=\"openCreateUserModal(createUserModal)\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"users-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <label for=\"search\" class=\"form-label\">Search</label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"search\"\n formControlName=\"search\"\n placeholder=\"Search by username, email, or name\">\n </div>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isStaff\" class=\"form-label\">Staff Status</label>\n <select class=\"form-select\" id=\"isStaff\" formControlName=\"isStaff\">\n <option value=\"\">All Users</option>\n <option value=\"true\">Staff Only</option>\n <option value=\"false\">Regular Users</option>\n </select>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isActive\" class=\"form-label\">Account Status</label>\n <select class=\"form-select\" id=\"isActive\" formControlName=\"isActive\">\n <option value=\"\">All Accounts</option>\n <option value=\"true\">Active Only</option>\n <option value=\"false\">Inactive Only</option>\n </select>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Success/Error Messages -->\n @if (successMessage()) {\n <div class=\"alert alert-success alert-dismissible\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ successMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n @if (errorMessage()) {\n <div class=\"alert alert-danger alert-dismissible\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n <!-- Users Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Users\n <span class=\"badge bg-primary ms-2\">{{ users().length }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasResults()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th>User</th>\n <th>Contact</th>\n <th>Status</th>\n <th>Joined</th>\n <th>Last Login</th>\n <th class=\"text-end\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (user of users(); track user.id) {\n <tr>\n <td>\n <div class=\"d-flex align-items-center\">\n <div>\n <div class=\"fw-semibold\">{{ getUserDisplayName(user) }}</div>\n <small class=\"text-muted\">@{{ user.username }}</small>\n </div>\n </div>\n </td>\n <td>\n <div>{{ user.email }}</div>\n </td>\n <td>\n <div class=\"d-flex gap-1 flex-wrap\">\n @if (user.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n @if (user.isStaff) {\n <span class=\"badge bg-warning\">Staff</span>\n }\n </div>\n </td>\n <td>{{ formatDate(user.dateJoined) }}</td>\n <td>{{ formatDate(user.lastLogin) }}</td>\n <td class=\"text-end\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditUserModal(editUserModal, user)\"\n title=\"Edit user\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning\"\n (click)=\"openPasswordResetModal(passwordResetModal, user)\"\n title=\"Reset password\">\n <i class=\"bi bi-shield-lock\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isActive ? 'btn-outline-secondary' : 'btn-outline-success'\"\n (click)=\"toggleUserStatus(user)\"\n [title]=\"user.isActive ? 'Deactivate user' : 'Activate user'\">\n <i class=\"bi\" [class]=\"user.isActive ? 'bi-person-dash' : 'bi-person-check'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isStaff ? 'btn-outline-secondary' : 'btn-outline-info'\"\n (click)=\"toggleStaffStatus(user)\"\n [title]=\"user.isStaff ? 'Remove staff privileges' : 'Grant staff privileges'\">\n <i class=\"bi\" [class]=\"user.isStaff ? 'bi-person-x' : 'bi-person-badge'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-danger\"\n (click)=\"deleteUser(user)\"\n title=\"Delete user\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"text-muted\">\n Showing {{ showingFrom() }} to {{ showingTo() }} of {{ totalUsers() }} users\n </div>\n <nav>\n <ul class=\"pagination pagination-sm mb-0\">\n <li class=\"page-item\" [class.disabled]=\"!canGoToPreviousPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() - 1)\" [disabled]=\"!canGoToPreviousPage()\">\n <i class=\"bi bi-chevron-left\"></i>\n </button>\n </li>\n @for (page of pages(); track page) {\n <li class=\"page-item\" [class.active]=\"page === currentPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(page)\">{{ page }}</button>\n </li>\n }\n <li class=\"page-item\" [class.disabled]=\"!canGoToNextPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() + 1)\" [disabled]=\"!canGoToNextPage()\">\n <i class=\"bi bi-chevron-right\"></i>\n </button>\n </li>\n </ul>\n </nav>\n </div>\n }\n } @else {\n <div class=\"text-center p-4\">\n <i class=\"bi bi-people display-4 text-muted\"></i>\n <p class=\"text-muted mt-2\">No users found</p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Create User Modal -->\n<ng-template #createUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-person-plus me-2\"></i>Create User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n <form #createForm=\"ngForm\" (ngSubmit)=\"createUser(createForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"createUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createUsername\"\n name=\"username\"\n ngModel\n required\n #usernameField=\"ngModel\">\n @if (usernameField.invalid && usernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"createEmail\"\n name=\"email\"\n ngModel\n required\n email\n #emailField=\"ngModel\">\n @if (emailField.invalid && emailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createFirstName\"\n name=\"firstName\"\n ngModel\n required\n #firstNameField=\"ngModel\">\n @if (firstNameField.invalid && firstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createLastName\"\n name=\"lastName\"\n ngModel\n required\n #lastNameField=\"ngModel\">\n @if (lastNameField.invalid && lastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createPassword\" class=\"form-label\">Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPassword\"\n name=\"password\"\n ngModel\n required\n minlength=\"8\"\n #passwordField=\"ngModel\">\n @if (passwordField.invalid && passwordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createPasswordConfirm\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPasswordConfirm\"\n name=\"password_confirm\"\n ngModel\n required\n #passwordConfirmField=\"ngModel\">\n @if (passwordConfirmField.invalid && passwordConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsStaff\"\n name=\"isStaff\"\n ngModel>\n <label class=\"form-check-label\" for=\"createIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsActive\"\n name=\"isActive\"\n ngModel\n checked>\n <label class=\"form-check-label\" for=\"createIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"createUser(createForm.value)\"\n [disabled]=\"createForm.invalid || !canCreateUser()\">\n @if (isCreatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n</ng-template>\n\n<!-- Edit User Modal -->\n<ng-template #editUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil me-2\"></i>Edit User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <form #editForm=\"ngForm\" id=\"editUserForm\" (ngSubmit)=\"updateUser(selectedUser()!.id!, editForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"editUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editUsername\"\n name=\"username\"\n [ngModel]=\"selectedUser()!.username\"\n required\n #editUsernameField=\"ngModel\">\n @if (editUsernameField.invalid && editUsernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"editEmail\"\n name=\"email\"\n [ngModel]=\"selectedUser()!.email\"\n required\n email\n #editEmailField=\"ngModel\">\n @if (editEmailField.invalid && editEmailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"editFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editFirstName\"\n name=\"firstName\"\n [ngModel]=\"selectedUser()!.firstName\"\n required\n #editFirstNameField=\"ngModel\">\n @if (editFirstNameField.invalid && editFirstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editLastName\"\n name=\"lastName\"\n [ngModel]=\"selectedUser()!.lastName\"\n required\n #editLastNameField=\"ngModel\">\n @if (editLastNameField.invalid && editLastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsStaff\"\n name=\"isStaff\"\n [ngModel]=\"selectedUser()!.isStaff\">\n <label class=\"form-check-label\" for=\"editIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsActive\"\n name=\"isActive\"\n [ngModel]=\"selectedUser()!.isActive\">\n <label class=\"form-check-label\" for=\"editIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n form=\"editUserForm\"\n [disabled]=\"!canUpdateUser()\">\n @if (isUpdatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>Update User\n </button>\n }\n </div>\n</ng-template>\n\n<!-- Password Reset Modal -->\n<ng-template #passwordResetModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-shield-lock me-2\"></i>Reset Password\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n You are about to reset the password for user <strong>{{ selectedUserDisplayName() }}</strong>.\n </div>\n \n <form #resetForm=\"ngForm\" id=\"resetPasswordForm\" (ngSubmit)=\"resetUserPassword(selectedUser()!.id!, resetForm.value)\">\n <div class=\"mb-3\">\n <label for=\"resetNewPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetNewPassword\"\n name=\"new_password\"\n ngModel\n required\n minlength=\"8\"\n #resetPasswordField=\"ngModel\">\n @if (resetPasswordField.invalid && resetPasswordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"resetConfirmPassword\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetConfirmPassword\"\n name=\"confirm_password\"\n ngModel\n required\n #resetConfirmField=\"ngModel\">\n @if (resetConfirmField.invalid && resetConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n <div class=\"mb-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"forcePasswordChange\"\n name=\"force_password_change\"\n ngModel>\n <label class=\"form-check-label\" for=\"forcePasswordChange\">\n Force user to change password on next login\n </label>\n </div>\n </div>\n <div class=\"mb-3\">\n <label for=\"resetReason\" class=\"form-label\">Reason (optional)</label>\n <textarea\n class=\"form-control\"\n id=\"resetReason\"\n name=\"reason\"\n ngModel\n rows=\"2\"\n placeholder=\"Optional reason for password reset\"></textarea>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-warning\"\n form=\"resetPasswordForm\"\n [disabled]=\"!canResetPassword()\">\n @if (isResettingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-lock me-1\"></i>Reset Password\n </button>\n }\n </div>\n</ng-template>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: NgbModule }] });
|
|
2654
2694
|
}
|
|
2655
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
2695
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementComponent, decorators: [{
|
|
2656
2696
|
type: Component,
|
|
2657
2697
|
args: [{ selector: 'ccc-user-management', standalone: true, imports: [CommonModule, ReactiveFormsModule, FormsModule, NgbModule], template: "<div class=\"user-management-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-people-fill me-2 text-primary\"></i>User Management\n </h4>\n <span class=\"badge bg-primary ms-3\">{{ totalUsers() }} users</span>\n </div>\n\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n (click)=\"openCreateUserModal(createUserModal)\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"users-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <label for=\"search\" class=\"form-label\">Search</label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"search\"\n formControlName=\"search\"\n placeholder=\"Search by username, email, or name\">\n </div>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isStaff\" class=\"form-label\">Staff Status</label>\n <select class=\"form-select\" id=\"isStaff\" formControlName=\"isStaff\">\n <option value=\"\">All Users</option>\n <option value=\"true\">Staff Only</option>\n <option value=\"false\">Regular Users</option>\n </select>\n </div>\n\n <div class=\"col-md-4\">\n <label for=\"isActive\" class=\"form-label\">Account Status</label>\n <select class=\"form-select\" id=\"isActive\" formControlName=\"isActive\">\n <option value=\"\">All Accounts</option>\n <option value=\"true\">Active Only</option>\n <option value=\"false\">Inactive Only</option>\n </select>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Success/Error Messages -->\n @if (successMessage()) {\n <div class=\"alert alert-success alert-dismissible\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ successMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n @if (errorMessage()) {\n <div class=\"alert alert-danger alert-dismissible\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n <button type=\"button\" class=\"btn-close\" (click)=\"clearMessages()\"></button>\n </div>\n }\n\n <!-- Users Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Users\n <span class=\"badge bg-primary ms-2\">{{ users().length }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasResults()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th>User</th>\n <th>Contact</th>\n <th>Status</th>\n <th>Joined</th>\n <th>Last Login</th>\n <th class=\"text-end\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (user of users(); track user.id) {\n <tr>\n <td>\n <div class=\"d-flex align-items-center\">\n <div>\n <div class=\"fw-semibold\">{{ getUserDisplayName(user) }}</div>\n <small class=\"text-muted\">@{{ user.username }}</small>\n </div>\n </div>\n </td>\n <td>\n <div>{{ user.email }}</div>\n </td>\n <td>\n <div class=\"d-flex gap-1 flex-wrap\">\n @if (user.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n @if (user.isStaff) {\n <span class=\"badge bg-warning\">Staff</span>\n }\n </div>\n </td>\n <td>{{ formatDate(user.dateJoined) }}</td>\n <td>{{ formatDate(user.lastLogin) }}</td>\n <td class=\"text-end\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditUserModal(editUserModal, user)\"\n title=\"Edit user\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning\"\n (click)=\"openPasswordResetModal(passwordResetModal, user)\"\n title=\"Reset password\">\n <i class=\"bi bi-shield-lock\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isActive ? 'btn-outline-secondary' : 'btn-outline-success'\"\n (click)=\"toggleUserStatus(user)\"\n [title]=\"user.isActive ? 'Deactivate user' : 'Activate user'\">\n <i class=\"bi\" [class]=\"user.isActive ? 'bi-person-dash' : 'bi-person-check'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"user.isStaff ? 'btn-outline-secondary' : 'btn-outline-info'\"\n (click)=\"toggleStaffStatus(user)\"\n [title]=\"user.isStaff ? 'Remove staff privileges' : 'Grant staff privileges'\">\n <i class=\"bi\" [class]=\"user.isStaff ? 'bi-person-x' : 'bi-person-badge'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-danger\"\n (click)=\"deleteUser(user)\"\n title=\"Delete user\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"text-muted\">\n Showing {{ showingFrom() }} to {{ showingTo() }} of {{ totalUsers() }} users\n </div>\n <nav>\n <ul class=\"pagination pagination-sm mb-0\">\n <li class=\"page-item\" [class.disabled]=\"!canGoToPreviousPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() - 1)\" [disabled]=\"!canGoToPreviousPage()\">\n <i class=\"bi bi-chevron-left\"></i>\n </button>\n </li>\n @for (page of pages(); track page) {\n <li class=\"page-item\" [class.active]=\"page === currentPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(page)\">{{ page }}</button>\n </li>\n }\n <li class=\"page-item\" [class.disabled]=\"!canGoToNextPage()\">\n <button class=\"page-link\" (click)=\"onPageChange(currentPage() + 1)\" [disabled]=\"!canGoToNextPage()\">\n <i class=\"bi bi-chevron-right\"></i>\n </button>\n </li>\n </ul>\n </nav>\n </div>\n }\n } @else {\n <div class=\"text-center p-4\">\n <i class=\"bi bi-people display-4 text-muted\"></i>\n <p class=\"text-muted mt-2\">No users found</p>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Create User Modal -->\n<ng-template #createUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-person-plus me-2\"></i>Create User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n <form #createForm=\"ngForm\" (ngSubmit)=\"createUser(createForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"createUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createUsername\"\n name=\"username\"\n ngModel\n required\n #usernameField=\"ngModel\">\n @if (usernameField.invalid && usernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"createEmail\"\n name=\"email\"\n ngModel\n required\n email\n #emailField=\"ngModel\">\n @if (emailField.invalid && emailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createFirstName\"\n name=\"firstName\"\n ngModel\n required\n #firstNameField=\"ngModel\">\n @if (firstNameField.invalid && firstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"createLastName\"\n name=\"lastName\"\n ngModel\n required\n #lastNameField=\"ngModel\">\n @if (lastNameField.invalid && lastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"createPassword\" class=\"form-label\">Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPassword\"\n name=\"password\"\n ngModel\n required\n minlength=\"8\"\n #passwordField=\"ngModel\">\n @if (passwordField.invalid && passwordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"createPasswordConfirm\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"createPasswordConfirm\"\n name=\"password_confirm\"\n ngModel\n required\n #passwordConfirmField=\"ngModel\">\n @if (passwordConfirmField.invalid && passwordConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsStaff\"\n name=\"isStaff\"\n ngModel>\n <label class=\"form-check-label\" for=\"createIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"createIsActive\"\n name=\"isActive\"\n ngModel\n checked>\n <label class=\"form-check-label\" for=\"createIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"createUser(createForm.value)\"\n [disabled]=\"createForm.invalid || !canCreateUser()\">\n @if (isCreatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-person-plus me-1\"></i>Create User\n </button>\n </div>\n</ng-template>\n\n<!-- Edit User Modal -->\n<ng-template #editUserModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil me-2\"></i>Edit User\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <form #editForm=\"ngForm\" id=\"editUserForm\" (ngSubmit)=\"updateUser(selectedUser()!.id!, editForm.value)\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"editUsername\" class=\"form-label\">Username</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editUsername\"\n name=\"username\"\n [ngModel]=\"selectedUser()!.username\"\n required\n #editUsernameField=\"ngModel\">\n @if (editUsernameField.invalid && editUsernameField.touched) {\n <div class=\"text-danger small\">Username is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editEmail\" class=\"form-label\">Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"editEmail\"\n name=\"email\"\n [ngModel]=\"selectedUser()!.email\"\n required\n email\n #editEmailField=\"ngModel\">\n @if (editEmailField.invalid && editEmailField.touched) {\n <div class=\"text-danger small\">Valid email is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <label for=\"editFirstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editFirstName\"\n name=\"firstName\"\n [ngModel]=\"selectedUser()!.firstName\"\n required\n #editFirstNameField=\"ngModel\">\n @if (editFirstNameField.invalid && editFirstNameField.touched) {\n <div class=\"text-danger small\">First name is required</div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"editLastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"editLastName\"\n name=\"lastName\"\n [ngModel]=\"selectedUser()!.lastName\"\n required\n #editLastNameField=\"ngModel\">\n @if (editLastNameField.invalid && editLastNameField.touched) {\n <div class=\"text-danger small\">Last name is required</div>\n }\n </div>\n </div>\n <div class=\"row g-3 mt-1\">\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsStaff\"\n name=\"isStaff\"\n [ngModel]=\"selectedUser()!.isStaff\">\n <label class=\"form-check-label\" for=\"editIsStaff\">\n Staff privileges\n </label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"editIsActive\"\n name=\"isActive\"\n [ngModel]=\"selectedUser()!.isActive\">\n <label class=\"form-check-label\" for=\"editIsActive\">\n Active account\n </label>\n </div>\n </div>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n form=\"editUserForm\"\n [disabled]=\"!canUpdateUser()\">\n @if (isUpdatingUser()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>Update User\n </button>\n }\n </div>\n</ng-template>\n\n<!-- Password Reset Modal -->\n<ng-template #passwordResetModal let-modal>\n <div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-shield-lock me-2\"></i>Reset Password\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"modal.dismiss()\"></button>\n </div>\n <div class=\"modal-body\">\n @if (hasSelectedUser()) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n You are about to reset the password for user <strong>{{ selectedUserDisplayName() }}</strong>.\n </div>\n \n <form #resetForm=\"ngForm\" id=\"resetPasswordForm\" (ngSubmit)=\"resetUserPassword(selectedUser()!.id!, resetForm.value)\">\n <div class=\"mb-3\">\n <label for=\"resetNewPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetNewPassword\"\n name=\"new_password\"\n ngModel\n required\n minlength=\"8\"\n #resetPasswordField=\"ngModel\">\n @if (resetPasswordField.invalid && resetPasswordField.touched) {\n <div class=\"text-danger small\">Password must be at least 8 characters</div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"resetConfirmPassword\" class=\"form-label\">Confirm Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"resetConfirmPassword\"\n name=\"confirm_password\"\n ngModel\n required\n #resetConfirmField=\"ngModel\">\n @if (resetConfirmField.invalid && resetConfirmField.touched) {\n <div class=\"text-danger small\">Password confirmation is required</div>\n }\n </div>\n <div class=\"mb-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"forcePasswordChange\"\n name=\"force_password_change\"\n ngModel>\n <label class=\"form-check-label\" for=\"forcePasswordChange\">\n Force user to change password on next login\n </label>\n </div>\n </div>\n <div class=\"mb-3\">\n <label for=\"resetReason\" class=\"form-label\">Reason (optional)</label>\n <textarea\n class=\"form-control\"\n id=\"resetReason\"\n name=\"reason\"\n ngModel\n rows=\"2\"\n placeholder=\"Optional reason for password reset\"></textarea>\n </div>\n </form>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"modal.dismiss()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n @if (hasSelectedUser()) {\n <button\n type=\"submit\"\n class=\"btn btn-warning\"\n form=\"resetPasswordForm\"\n [disabled]=\"!canResetPassword()\">\n @if (isResettingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-lock me-1\"></i>Reset Password\n </button>\n }\n </div>\n</ng-template>" }]
|
|
2658
2698
|
}], ctorParameters: () => [] });
|
|
@@ -2973,10 +3013,10 @@ class LabGroupsComponent {
|
|
|
2973
3013
|
this.loadGroupMembers(group.id);
|
|
2974
3014
|
}
|
|
2975
3015
|
}
|
|
2976
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
2977
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: LabGroupsComponent, isStandalone: true, selector: "ccc-lab-groups", ngImport: i0, template: "<div class=\"lab-groups-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n </h4>\n </div>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>New Lab Group\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"groups-content p-4\">\n <!-- Create Group Form -->\n @if (showCreateForm()) {\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create New Lab Group\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"createGroupForm\" (ngSubmit)=\"createLabGroup()\" class=\"row g-3\">\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"groupName\" \n formControlName=\"name\"\n [class.is-invalid]=\"createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched\"\n placeholder=\"Group name\">\n <label for=\"groupName\">Group Name *</label>\n </div>\n @if (createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n @if (createGroupForm.get('name')?.errors?.['required']) {\n Group name is required\n }\n @if (createGroupForm.get('name')?.errors?.['minlength']) {\n Group name must be at least 2 characters\n }\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mt-3\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"allowInvites\" \n formControlName=\"allowMemberInvites\">\n <label class=\"form-check-label\" for=\"allowInvites\">\n <i class=\"bi bi-envelope me-1\"></i>Allow members to invite others\n </label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"groupDescription\" \n formControlName=\"description\"\n style=\"height: 100px\"\n placeholder=\"Description\"></textarea>\n <label for=\"groupDescription\">Description (optional)</label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"d-flex gap-2\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"createGroupForm.invalid || isCreatingGroup()\">\n @if (isCreatingGroup()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Create Group\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"toggleCreateForm()\">\n Cancel\n </button>\n </div>\n </div>\n </form>\n </div>\n </div>\n }\n\n <!-- Search & Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-12\">\n <div class=\"form-floating\">\n <input \n type=\"search\" \n class=\"form-control\" \n id=\"groupSearch\" \n formControlName=\"search\"\n placeholder=\"Search groups...\">\n <label for=\"groupSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Groups\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Groups List -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n <span class=\"badge bg-primary ms-2\">{{ labGroupsData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasLabGroups()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\"><i class=\"bi bi-tag me-1\"></i>Group Name</th>\n <th scope=\"col\"><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th scope=\"col\"><i class=\"bi bi-people me-1\"></i>Members</th>\n <th scope=\"col\"><i class=\"bi bi-check-circle me-1\"></i>Status</th>\n <th scope=\"col\"><i class=\"bi bi-calendar me-1\"></i>Created</th>\n <th scope=\"col\" style=\"width: 120px;\"><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (group of labGroupsData().results; track group.id) {\n <tr>\n <td>\n <strong>{{ group.name }}</strong>\n @if (group.description) {\n <br><small class=\"text-muted\">{{ group.description }}</small>\n }\n </td>\n <td>{{ group.creatorName || 'Unknown' }}</td>\n <td>\n <span class=\"badge bg-info\">{{ group.memberCount }}</span>\n </td>\n <td>\n @if (group.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-secondary\">Inactive</span>\n }\n </td>\n <td>\n <small>{{ group.createdAt | date:'short' }}</small>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"viewGroupMembers(group)\"\n title=\"View members\">\n <i class=\"bi bi-people\"></i>\n </button>\n @if (group.isMember && !group.isCreator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-warning\" \n (click)=\"leaveGroup(group)\"\n title=\"Leave group\">\n <i class=\"bi bi-box-arrow-right\"></i>\n </button>\n }\n @if (group.canManage) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"deleteGroup(group)\"\n title=\"Delete group\">\n <i class=\"bi bi-trash\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-people display-1 mb-3\"></i>\n <h5>No Lab Groups Found</h5>\n <p>\n @if (hasSearchValue()) {\n Try adjusting your search criteria or create a new lab group.\n } @else {\n Get started by creating your first lab group.\n }\n </p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Lab Group\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Group Members Modal -->\n@if (selectedGroupForMembers()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-people me-2\"></i>{{ currentGroupName() }} - Members\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeGroupDetails()\"></button>\n </div>\n <div class=\"modal-body\">\n <!-- Invite Form -->\n @if (canInviteToCurrentGroup()) {\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Invite Member\n </h6>\n <button \n type=\"button\"\n class=\"btn btn-sm btn-outline-primary\" \n (click)=\"toggleInviteForm()\">\n @if (showInviteForm()) {\n Cancel\n } @else {\n <i class=\"bi bi-plus me-1\"></i>Invite\n }\n </button>\n </div>\n @if (showInviteForm()) {\n <div class=\"card-body\">\n <form [formGroup]=\"inviteForm\" (ngSubmit)=\"inviteMember()\" class=\"row g-3\">\n <div class=\"col-md-8\">\n <div class=\"form-floating\">\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"inviteEmail\" \n formControlName=\"invitedEmail\"\n [class.is-invalid]=\"inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched\"\n placeholder=\"Email address\">\n <label for=\"inviteEmail\">Email Address *</label>\n </div>\n @if (inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched) {\n <div class=\"invalid-feedback\">\n @if (inviteForm.get('invitedEmail')?.errors?.['required']) {\n Email is required\n }\n @if (inviteForm.get('invitedEmail')?.errors?.['email']) {\n Please enter a valid email address\n }\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button \n type=\"submit\" \n class=\"btn btn-primary w-100 h-100\"\n [disabled]=\"inviteForm.invalid || isInviting()\">\n @if (isInviting()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Send Invite\n </button>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"inviteMessage\" \n formControlName=\"message\"\n style=\"height: 80px\"\n placeholder=\"Message\"></textarea>\n <label for=\"inviteMessage\">Message (optional)</label>\n </div>\n </div>\n </form>\n </div>\n }\n </div>\n }\n\n <!-- Current Members -->\n <div class=\"card mb-3\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Current Members ({{ groupMembersCount() }})\n </h6>\n @if (hasSubGroups()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"directMembersOnly\"\n [checked]=\"directMembersOnly()\"\n (change)=\"toggleDirectMembersOnly()\">\n <label class=\"form-check-label small\" for=\"directMembersOnly\">\n Direct only\n </label>\n </div>\n }\n </div>\n </div>\n <div class=\"card-body p-0\">\n @if (hasGroupMembers()) {\n <div class=\"list-group list-group-flush\">\n @for (member of groupMembers(); track member.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ member.firstName }} {{ member.lastName }}</strong>\n <br>\n <small class=\"text-muted\">{{ member.email }}</small>\n @if (selectedGroupForMembers() && member.id === selectedGroupForMembers()!.creator) {\n <span class=\"badge bg-warning ms-2\">Creator</span>\n }\n </div>\n @if (canManageCurrentGroup() && selectedGroupForMembers() && member.id !== selectedGroupForMembers()!.creator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"removeMember(member.id)\"\n title=\"Remove member\">\n <i class=\"bi bi-person-dash\"></i>\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"text-center py-3 text-muted\">\n <i class=\"bi bi-people fs-4 mb-2\"></i>\n <div>No members found</div>\n </div>\n }\n </div>\n @if (showMemberPagination()) {\n <div class=\"card-footer\">\n <div class=\"d-flex justify-content-center\">\n <ngb-pagination\n [collectionSize]=\"memberTotal()\"\n [page]=\"memberPage()\"\n [pageSize]=\"memberPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onMemberPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n </div>\n }\n </div>\n\n <!-- Pending Invitations -->\n @if (hasPendingInvitations()) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-clock me-2\"></i>Pending Invitations ({{ pendingInvitationsCount() }})\n </h6>\n </div>\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\">\n @for (invitation of pendingInvitations(); track invitation.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ invitation.invitedEmail }}</strong>\n <br>\n <small class=\"text-muted\">\n Invited by {{ invitation.inviterName }} on {{ invitation.createdAt | date:'short' }}\n </small>\n @if (invitation.message) {\n <br>\n <small class=\"text-info\">{{ invitation.message }}</small>\n }\n </div>\n @if (canManageCurrentGroup()) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"cancelInvitation(invitation.id)\"\n title=\"Cancel invitation\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeGroupDetails()\">\n Close\n </button>\n </div>\n </div>\n </div>\n </div>\n}", styles: [".lab-groups-container .groups-content{max-width:1200px;margin:0 auto}@media (max-width: 768px){.lab-groups-container .groups-content{padding:1rem!important}.lab-groups-container .btn-group{flex-direction:column;width:100%}.lab-groups-container .btn-group .btn{border-radius:.25rem!important;margin-bottom:.25rem}.lab-groups-container .modal-dialog{margin:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }] });
|
|
3016
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LabGroupsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3017
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: LabGroupsComponent, isStandalone: true, selector: "ccc-lab-groups", ngImport: i0, template: "<div class=\"lab-groups-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n </h4>\n </div>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>New Lab Group\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"groups-content p-4\">\n <!-- Create Group Form -->\n @if (showCreateForm()) {\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create New Lab Group\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"createGroupForm\" (ngSubmit)=\"createLabGroup()\" class=\"row g-3\">\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"groupName\" \n formControlName=\"name\"\n [class.is-invalid]=\"createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched\"\n placeholder=\"Group name\">\n <label for=\"groupName\">Group Name *</label>\n </div>\n @if (createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n @if (createGroupForm.get('name')?.errors?.['required']) {\n Group name is required\n }\n @if (createGroupForm.get('name')?.errors?.['minlength']) {\n Group name must be at least 2 characters\n }\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mt-3\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"allowInvites\" \n formControlName=\"allowMemberInvites\">\n <label class=\"form-check-label\" for=\"allowInvites\">\n <i class=\"bi bi-envelope me-1\"></i>Allow members to invite others\n </label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"groupDescription\" \n formControlName=\"description\"\n style=\"height: 100px\"\n placeholder=\"Description\"></textarea>\n <label for=\"groupDescription\">Description (optional)</label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"d-flex gap-2\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"createGroupForm.invalid || isCreatingGroup()\">\n @if (isCreatingGroup()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Create Group\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"toggleCreateForm()\">\n Cancel\n </button>\n </div>\n </div>\n </form>\n </div>\n </div>\n }\n\n <!-- Search & Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-12\">\n <div class=\"form-floating\">\n <input \n type=\"search\" \n class=\"form-control\" \n id=\"groupSearch\" \n formControlName=\"search\"\n placeholder=\"Search groups...\">\n <label for=\"groupSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Groups\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Groups List -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n <span class=\"badge bg-primary ms-2\">{{ labGroupsData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasLabGroups()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\"><i class=\"bi bi-tag me-1\"></i>Group Name</th>\n <th scope=\"col\"><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th scope=\"col\"><i class=\"bi bi-people me-1\"></i>Members</th>\n <th scope=\"col\"><i class=\"bi bi-check-circle me-1\"></i>Status</th>\n <th scope=\"col\"><i class=\"bi bi-calendar me-1\"></i>Created</th>\n <th scope=\"col\" style=\"width: 120px;\"><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (group of labGroupsData().results; track group.id) {\n <tr>\n <td>\n <strong>{{ group.name }}</strong>\n @if (group.description) {\n <br><small class=\"text-muted\">{{ group.description }}</small>\n }\n </td>\n <td>{{ group.creatorName || 'Unknown' }}</td>\n <td>\n <span class=\"badge bg-info\">{{ group.memberCount }}</span>\n </td>\n <td>\n @if (group.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-secondary\">Inactive</span>\n }\n </td>\n <td>\n <small>{{ group.createdAt | date:'short' }}</small>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"viewGroupMembers(group)\"\n title=\"View members\">\n <i class=\"bi bi-people\"></i>\n </button>\n @if (group.isMember && !group.isCreator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-warning\" \n (click)=\"leaveGroup(group)\"\n title=\"Leave group\">\n <i class=\"bi bi-box-arrow-right\"></i>\n </button>\n }\n @if (group.canManage) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"deleteGroup(group)\"\n title=\"Delete group\">\n <i class=\"bi bi-trash\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-people display-1 mb-3\"></i>\n <h5>No Lab Groups Found</h5>\n <p>\n @if (hasSearchValue()) {\n Try adjusting your search criteria or create a new lab group.\n } @else {\n Get started by creating your first lab group.\n }\n </p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Lab Group\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Group Members Modal -->\n@if (selectedGroupForMembers()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-people me-2\"></i>{{ currentGroupName() }} - Members\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeGroupDetails()\"></button>\n </div>\n <div class=\"modal-body\">\n <!-- Invite Form -->\n @if (canInviteToCurrentGroup()) {\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Invite Member\n </h6>\n <button \n type=\"button\"\n class=\"btn btn-sm btn-outline-primary\" \n (click)=\"toggleInviteForm()\">\n @if (showInviteForm()) {\n Cancel\n } @else {\n <i class=\"bi bi-plus me-1\"></i>Invite\n }\n </button>\n </div>\n @if (showInviteForm()) {\n <div class=\"card-body\">\n <form [formGroup]=\"inviteForm\" (ngSubmit)=\"inviteMember()\" class=\"row g-3\">\n <div class=\"col-md-8\">\n <div class=\"form-floating\">\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"inviteEmail\" \n formControlName=\"invitedEmail\"\n [class.is-invalid]=\"inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched\"\n placeholder=\"Email address\">\n <label for=\"inviteEmail\">Email Address *</label>\n </div>\n @if (inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched) {\n <div class=\"invalid-feedback\">\n @if (inviteForm.get('invitedEmail')?.errors?.['required']) {\n Email is required\n }\n @if (inviteForm.get('invitedEmail')?.errors?.['email']) {\n Please enter a valid email address\n }\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button \n type=\"submit\" \n class=\"btn btn-primary w-100 h-100\"\n [disabled]=\"inviteForm.invalid || isInviting()\">\n @if (isInviting()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Send Invite\n </button>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"inviteMessage\" \n formControlName=\"message\"\n style=\"height: 80px\"\n placeholder=\"Message\"></textarea>\n <label for=\"inviteMessage\">Message (optional)</label>\n </div>\n </div>\n </form>\n </div>\n }\n </div>\n }\n\n <!-- Current Members -->\n <div class=\"card mb-3\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Current Members ({{ groupMembersCount() }})\n </h6>\n @if (hasSubGroups()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"directMembersOnly\"\n [checked]=\"directMembersOnly()\"\n (change)=\"toggleDirectMembersOnly()\">\n <label class=\"form-check-label small\" for=\"directMembersOnly\">\n Direct only\n </label>\n </div>\n }\n </div>\n </div>\n <div class=\"card-body p-0\">\n @if (hasGroupMembers()) {\n <div class=\"list-group list-group-flush\">\n @for (member of groupMembers(); track member.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ member.firstName }} {{ member.lastName }}</strong>\n <br>\n <small class=\"text-muted\">{{ member.email }}</small>\n @if (selectedGroupForMembers() && member.id === selectedGroupForMembers()!.creator) {\n <span class=\"badge bg-warning ms-2\">Creator</span>\n }\n </div>\n @if (canManageCurrentGroup() && selectedGroupForMembers() && member.id !== selectedGroupForMembers()!.creator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"removeMember(member.id)\"\n title=\"Remove member\">\n <i class=\"bi bi-person-dash\"></i>\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"text-center py-3 text-muted\">\n <i class=\"bi bi-people fs-4 mb-2\"></i>\n <div>No members found</div>\n </div>\n }\n </div>\n @if (showMemberPagination()) {\n <div class=\"card-footer\">\n <div class=\"d-flex justify-content-center\">\n <ngb-pagination\n [collectionSize]=\"memberTotal()\"\n [page]=\"memberPage()\"\n [pageSize]=\"memberPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onMemberPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n </div>\n }\n </div>\n\n <!-- Pending Invitations -->\n @if (hasPendingInvitations()) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-clock me-2\"></i>Pending Invitations ({{ pendingInvitationsCount() }})\n </h6>\n </div>\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\">\n @for (invitation of pendingInvitations(); track invitation.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ invitation.invitedEmail }}</strong>\n <br>\n <small class=\"text-muted\">\n Invited by {{ invitation.inviterName }} on {{ invitation.createdAt | date:'short' }}\n </small>\n @if (invitation.message) {\n <br>\n <small class=\"text-info\">{{ invitation.message }}</small>\n }\n </div>\n @if (canManageCurrentGroup()) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"cancelInvitation(invitation.id)\"\n title=\"Cancel invitation\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeGroupDetails()\">\n Close\n </button>\n </div>\n </div>\n </div>\n </div>\n}", styles: [".lab-groups-container .groups-content{max-width:1200px;margin:0 auto}@media (max-width: 768px){.lab-groups-container .groups-content{padding:1rem!important}.lab-groups-container .btn-group{flex-direction:column;width:100%}.lab-groups-container .btn-group .btn{border-radius:.25rem!important;margin-bottom:.25rem}.lab-groups-container .modal-dialog{margin:.5rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2$1.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }] });
|
|
2978
3018
|
}
|
|
2979
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3019
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: LabGroupsComponent, decorators: [{
|
|
2980
3020
|
type: Component,
|
|
2981
3021
|
args: [{ selector: 'ccc-lab-groups', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"lab-groups-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n </h4>\n </div>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>New Lab Group\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"groups-content p-4\">\n <!-- Create Group Form -->\n @if (showCreateForm()) {\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create New Lab Group\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"createGroupForm\" (ngSubmit)=\"createLabGroup()\" class=\"row g-3\">\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"groupName\" \n formControlName=\"name\"\n [class.is-invalid]=\"createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched\"\n placeholder=\"Group name\">\n <label for=\"groupName\">Group Name *</label>\n </div>\n @if (createGroupForm.get('name')?.invalid && createGroupForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n @if (createGroupForm.get('name')?.errors?.['required']) {\n Group name is required\n }\n @if (createGroupForm.get('name')?.errors?.['minlength']) {\n Group name must be at least 2 characters\n }\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mt-3\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"allowInvites\" \n formControlName=\"allowMemberInvites\">\n <label class=\"form-check-label\" for=\"allowInvites\">\n <i class=\"bi bi-envelope me-1\"></i>Allow members to invite others\n </label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"groupDescription\" \n formControlName=\"description\"\n style=\"height: 100px\"\n placeholder=\"Description\"></textarea>\n <label for=\"groupDescription\">Description (optional)</label>\n </div>\n </div>\n <div class=\"col-12\">\n <div class=\"d-flex gap-2\">\n <button \n type=\"submit\" \n class=\"btn btn-primary\"\n [disabled]=\"createGroupForm.invalid || isCreatingGroup()\">\n @if (isCreatingGroup()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Create Group\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"toggleCreateForm()\">\n Cancel\n </button>\n </div>\n </div>\n </form>\n </div>\n </div>\n }\n\n <!-- Search & Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-12\">\n <div class=\"form-floating\">\n <input \n type=\"search\" \n class=\"form-control\" \n id=\"groupSearch\" \n formControlName=\"search\"\n placeholder=\"Search groups...\">\n <label for=\"groupSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Groups\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Groups List -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Lab Groups\n <span class=\"badge bg-primary ms-2\">{{ labGroupsData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasLabGroups()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\"><i class=\"bi bi-tag me-1\"></i>Group Name</th>\n <th scope=\"col\"><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th scope=\"col\"><i class=\"bi bi-people me-1\"></i>Members</th>\n <th scope=\"col\"><i class=\"bi bi-check-circle me-1\"></i>Status</th>\n <th scope=\"col\"><i class=\"bi bi-calendar me-1\"></i>Created</th>\n <th scope=\"col\" style=\"width: 120px;\"><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (group of labGroupsData().results; track group.id) {\n <tr>\n <td>\n <strong>{{ group.name }}</strong>\n @if (group.description) {\n <br><small class=\"text-muted\">{{ group.description }}</small>\n }\n </td>\n <td>{{ group.creatorName || 'Unknown' }}</td>\n <td>\n <span class=\"badge bg-info\">{{ group.memberCount }}</span>\n </td>\n <td>\n @if (group.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-secondary\">Inactive</span>\n }\n </td>\n <td>\n <small>{{ group.createdAt | date:'short' }}</small>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"viewGroupMembers(group)\"\n title=\"View members\">\n <i class=\"bi bi-people\"></i>\n </button>\n @if (group.isMember && !group.isCreator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-warning\" \n (click)=\"leaveGroup(group)\"\n title=\"Leave group\">\n <i class=\"bi bi-box-arrow-right\"></i>\n </button>\n }\n @if (group.canManage) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"deleteGroup(group)\"\n title=\"Delete group\">\n <i class=\"bi bi-trash\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-people display-1 mb-3\"></i>\n <h5>No Lab Groups Found</h5>\n <p>\n @if (hasSearchValue()) {\n Try adjusting your search criteria or create a new lab group.\n } @else {\n Get started by creating your first lab group.\n }\n </p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"toggleCreateForm()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Lab Group\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Group Members Modal -->\n@if (selectedGroupForMembers()) {\n <div class=\"modal-backdrop fade show\"></div>\n <div class=\"modal fade show d-block\" tabindex=\"-1\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-people me-2\"></i>{{ currentGroupName() }} - Members\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeGroupDetails()\"></button>\n </div>\n <div class=\"modal-body\">\n <!-- Invite Form -->\n @if (canInviteToCurrentGroup()) {\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Invite Member\n </h6>\n <button \n type=\"button\"\n class=\"btn btn-sm btn-outline-primary\" \n (click)=\"toggleInviteForm()\">\n @if (showInviteForm()) {\n Cancel\n } @else {\n <i class=\"bi bi-plus me-1\"></i>Invite\n }\n </button>\n </div>\n @if (showInviteForm()) {\n <div class=\"card-body\">\n <form [formGroup]=\"inviteForm\" (ngSubmit)=\"inviteMember()\" class=\"row g-3\">\n <div class=\"col-md-8\">\n <div class=\"form-floating\">\n <input \n type=\"email\" \n class=\"form-control\" \n id=\"inviteEmail\" \n formControlName=\"invitedEmail\"\n [class.is-invalid]=\"inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched\"\n placeholder=\"Email address\">\n <label for=\"inviteEmail\">Email Address *</label>\n </div>\n @if (inviteForm.get('invitedEmail')?.invalid && inviteForm.get('invitedEmail')?.touched) {\n <div class=\"invalid-feedback\">\n @if (inviteForm.get('invitedEmail')?.errors?.['required']) {\n Email is required\n }\n @if (inviteForm.get('invitedEmail')?.errors?.['email']) {\n Please enter a valid email address\n }\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button \n type=\"submit\" \n class=\"btn btn-primary w-100 h-100\"\n [disabled]=\"inviteForm.invalid || isInviting()\">\n @if (isInviting()) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n Send Invite\n </button>\n </div>\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea \n class=\"form-control\" \n id=\"inviteMessage\" \n formControlName=\"message\"\n style=\"height: 80px\"\n placeholder=\"Message\"></textarea>\n <label for=\"inviteMessage\">Message (optional)</label>\n </div>\n </div>\n </form>\n </div>\n }\n </div>\n }\n\n <!-- Current Members -->\n <div class=\"card mb-3\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-people me-2\"></i>Current Members ({{ groupMembersCount() }})\n </h6>\n @if (hasSubGroups()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"directMembersOnly\"\n [checked]=\"directMembersOnly()\"\n (change)=\"toggleDirectMembersOnly()\">\n <label class=\"form-check-label small\" for=\"directMembersOnly\">\n Direct only\n </label>\n </div>\n }\n </div>\n </div>\n <div class=\"card-body p-0\">\n @if (hasGroupMembers()) {\n <div class=\"list-group list-group-flush\">\n @for (member of groupMembers(); track member.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ member.firstName }} {{ member.lastName }}</strong>\n <br>\n <small class=\"text-muted\">{{ member.email }}</small>\n @if (selectedGroupForMembers() && member.id === selectedGroupForMembers()!.creator) {\n <span class=\"badge bg-warning ms-2\">Creator</span>\n }\n </div>\n @if (canManageCurrentGroup() && selectedGroupForMembers() && member.id !== selectedGroupForMembers()!.creator) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"removeMember(member.id)\"\n title=\"Remove member\">\n <i class=\"bi bi-person-dash\"></i>\n </button>\n }\n </div>\n }\n </div>\n } @else {\n <div class=\"text-center py-3 text-muted\">\n <i class=\"bi bi-people fs-4 mb-2\"></i>\n <div>No members found</div>\n </div>\n }\n </div>\n @if (showMemberPagination()) {\n <div class=\"card-footer\">\n <div class=\"d-flex justify-content-center\">\n <ngb-pagination\n [collectionSize]=\"memberTotal()\"\n [page]=\"memberPage()\"\n [pageSize]=\"memberPageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onMemberPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n </div>\n }\n </div>\n\n <!-- Pending Invitations -->\n @if (hasPendingInvitations()) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-clock me-2\"></i>Pending Invitations ({{ pendingInvitationsCount() }})\n </h6>\n </div>\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\">\n @for (invitation of pendingInvitations(); track invitation.id) {\n <div class=\"list-group-item d-flex justify-content-between align-items-center\">\n <div>\n <strong>{{ invitation.invitedEmail }}</strong>\n <br>\n <small class=\"text-muted\">\n Invited by {{ invitation.inviterName }} on {{ invitation.createdAt | date:'short' }}\n </small>\n @if (invitation.message) {\n <br>\n <small class=\"text-info\">{{ invitation.message }}</small>\n }\n </div>\n @if (canManageCurrentGroup()) {\n <button \n type=\"button\"\n class=\"btn btn-outline-danger btn-sm\" \n (click)=\"cancelInvitation(invitation.id)\"\n title=\"Cancel invitation\">\n <i class=\"bi bi-x-circle\"></i>\n </button>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeGroupDetails()\">\n Close\n </button>\n </div>\n </div>\n </div>\n </div>\n}", styles: [".lab-groups-container .groups-content{max-width:1200px;margin:0 auto}@media (max-width: 768px){.lab-groups-container .groups-content{padding:1rem!important}.lab-groups-container .btn-group{flex-direction:column;width:100%}.lab-groups-container .btn-group .btn{border-radius:.25rem!important;margin-bottom:.25rem}.lab-groups-container .modal-dialog{margin:.5rem}}\n"] }]
|
|
2982
3022
|
}], ctorParameters: () => [] });
|
|
@@ -3137,10 +3177,10 @@ class UserProfileComponent {
|
|
|
3137
3177
|
}
|
|
3138
3178
|
return null;
|
|
3139
3179
|
}
|
|
3140
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3141
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: UserProfileComponent, isStandalone: true, selector: "ccc-user-profile", ngImport: i0, template: "<div class=\"user-profile-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-person-circle me-2 text-primary\"></i>User Profile\n </h4>\n @if (isStaff()) {\n <span class=\"badge bg-warning ms-3\">Staff</span>\n }\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"profile-content p-4\">\n @if (isLoading()) {\n <div class=\"text-center\">\n <div class=\"spinner-border text-primary me-2\"></div>\n <span>Loading profile...</span>\n </div>\n } @else {\n <!-- User Info Card -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>Account Information\n </h5>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Full Name:</small>\n <div class=\"fw-semibold\">{{ fullName() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Username:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.username }}</div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Email:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.email }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Account Status:</small>\n <div>\n @if (currentUser()?.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Member Since:</small>\n <div class=\"fw-semibold\">{{ joinDate() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Last Login:</small>\n <div class=\"fw-semibold\">{{ lastLogin() }}</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Navigation Tabs -->\n <ul ngbNav #profileNav=\"ngbNav\" [activeId]=\"activeTab()\" (activeIdChange)=\"setActiveTab($event)\" class=\"nav-tabs\">\n <li [ngbNavItem]=\"'profile'\">\n <button ngbNavLink (click)=\"setActiveTab('profile')\">\n <i class=\"bi bi-person me-1\"></i>Profile Settings\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Profile Update Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-pencil-square me-2\"></i>Update Profile Information\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"profileForm\" (ngSubmit)=\"updateProfile()\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"firstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"firstName\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched\">\n @if (profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched) {\n <div class=\"invalid-feedback\">\n First name is required (max 30 characters)\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"lastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"lastName\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched\">\n @if (profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched) {\n <div class=\"invalid-feedback\">\n Last name is required (max 30 characters)\n </div>\n }\n </div>\n </div>\n <div class=\"mt-3\">\n <label for=\"currentPassword\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPassword\"\n formControlName=\"currentPassword\"\n placeholder=\"Enter your current password to confirm changes\"\n [class.is-invalid]=\"profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched\">\n @if (profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required to update profile\n </div>\n }\n </div>\n\n @if (profileMessage()) {\n <div class=\"alert alert-success mt-3\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ profileMessage() }}\n </div>\n }\n\n <div class=\"mt-3\">\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"profileForm.invalid || isUpdatingProfile()\">\n @if (isUpdatingProfile()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n Update Profile\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'password'\">\n <button ngbNavLink (click)=\"setActiveTab('password')\">\n <i class=\"bi bi-shield-lock me-1\"></i>Change Password\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Password Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-shield-lock me-2\"></i>Change Password\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"passwordForm\" (ngSubmit)=\"changePassword()\">\n <div class=\"mb-3\">\n <label for=\"currentPasswordChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPasswordChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched\">\n @if (passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\n [class.is-invalid]=\"passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched\">\n @if (passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Password must be at least 8 characters long\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">Confirm New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"confirmPassword\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"(passwordForm.get('confirmPassword')?.invalid || passwordForm.hasError('passwordMismatch')) && passwordForm.get('confirmPassword')?.touched\">\n @if (passwordForm.get('confirmPassword')?.invalid && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Please confirm your new password\n </div>\n }\n @if (passwordForm.hasError('passwordMismatch') && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Passwords do not match\n </div>\n }\n </div>\n\n @if (passwordMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ passwordMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"passwordForm.invalid || isChangingPassword()\">\n @if (isChangingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-check me-1\"></i>\n Change Password\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'email'\">\n <button ngbNavLink (click)=\"setActiveTab('email')\">\n <i class=\"bi bi-envelope me-1\"></i>Change Email\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Email Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Change Email Address\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Changing your email will require verification. You'll receive a confirmation email at your new address.\n </div>\n \n <form [formGroup]=\"emailChangeForm\" (ngSubmit)=\"requestEmailChange()\">\n <div class=\"mb-3\">\n <label for=\"currentEmail\" class=\"form-label\">Current Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"currentEmail\"\n [value]=\"currentUser()?.email\"\n readonly>\n </div>\n <div class=\"mb-3\">\n <label for=\"newEmail\" class=\"form-label\">New Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"newEmail\"\n formControlName=\"newEmail\"\n [class.is-invalid]=\"emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched\">\n @if (emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched) {\n <div class=\"invalid-feedback\">\n Please enter a valid email address\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"passwordEmailChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"passwordEmailChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched\">\n @if (emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n\n @if (emailMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ emailMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"emailChangeForm.invalid || isChangingEmail()\">\n @if (isChangingEmail()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-envelope-check me-1\"></i>\n Request Email Change\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"profileNav\" class=\"mt-3\"></div>\n\n <!-- Error Messages -->\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n </div>\n }\n }\n </div>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i2$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }] });
|
|
3180
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserProfileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3181
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: UserProfileComponent, isStandalone: true, selector: "ccc-user-profile", ngImport: i0, template: "<div class=\"user-profile-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-person-circle me-2 text-primary\"></i>User Profile\n </h4>\n @if (isStaff()) {\n <span class=\"badge bg-warning ms-3\">Staff</span>\n }\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"profile-content p-4\">\n @if (isLoading()) {\n <div class=\"text-center\">\n <div class=\"spinner-border text-primary me-2\"></div>\n <span>Loading profile...</span>\n </div>\n } @else {\n <!-- User Info Card -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>Account Information\n </h5>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Full Name:</small>\n <div class=\"fw-semibold\">{{ fullName() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Username:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.username }}</div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Email:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.email }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Account Status:</small>\n <div>\n @if (currentUser()?.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Member Since:</small>\n <div class=\"fw-semibold\">{{ joinDate() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Last Login:</small>\n <div class=\"fw-semibold\">{{ lastLogin() }}</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Navigation Tabs -->\n <ul ngbNav #profileNav=\"ngbNav\" [activeId]=\"activeTab()\" (activeIdChange)=\"setActiveTab($event)\" class=\"nav-tabs\">\n <li [ngbNavItem]=\"'profile'\">\n <button ngbNavLink (click)=\"setActiveTab('profile')\">\n <i class=\"bi bi-person me-1\"></i>Profile Settings\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Profile Update Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-pencil-square me-2\"></i>Update Profile Information\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"profileForm\" (ngSubmit)=\"updateProfile()\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"firstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"firstName\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched\">\n @if (profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched) {\n <div class=\"invalid-feedback\">\n First name is required (max 30 characters)\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"lastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"lastName\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched\">\n @if (profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched) {\n <div class=\"invalid-feedback\">\n Last name is required (max 30 characters)\n </div>\n }\n </div>\n </div>\n <div class=\"mt-3\">\n <label for=\"currentPassword\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPassword\"\n formControlName=\"currentPassword\"\n placeholder=\"Enter your current password to confirm changes\"\n [class.is-invalid]=\"profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched\">\n @if (profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required to update profile\n </div>\n }\n </div>\n\n @if (profileMessage()) {\n <div class=\"alert alert-success mt-3\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ profileMessage() }}\n </div>\n }\n\n <div class=\"mt-3\">\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"profileForm.invalid || isUpdatingProfile()\">\n @if (isUpdatingProfile()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n Update Profile\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'password'\">\n <button ngbNavLink (click)=\"setActiveTab('password')\">\n <i class=\"bi bi-shield-lock me-1\"></i>Change Password\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Password Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-shield-lock me-2\"></i>Change Password\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"passwordForm\" (ngSubmit)=\"changePassword()\">\n <div class=\"mb-3\">\n <label for=\"currentPasswordChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPasswordChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched\">\n @if (passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\n [class.is-invalid]=\"passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched\">\n @if (passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Password must be at least 8 characters long\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">Confirm New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"confirmPassword\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"(passwordForm.get('confirmPassword')?.invalid || passwordForm.hasError('passwordMismatch')) && passwordForm.get('confirmPassword')?.touched\">\n @if (passwordForm.get('confirmPassword')?.invalid && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Please confirm your new password\n </div>\n }\n @if (passwordForm.hasError('passwordMismatch') && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Passwords do not match\n </div>\n }\n </div>\n\n @if (passwordMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ passwordMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"passwordForm.invalid || isChangingPassword()\">\n @if (isChangingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-check me-1\"></i>\n Change Password\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'email'\">\n <button ngbNavLink (click)=\"setActiveTab('email')\">\n <i class=\"bi bi-envelope me-1\"></i>Change Email\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Email Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Change Email Address\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Changing your email will require verification. You'll receive a confirmation email at your new address.\n </div>\n \n <form [formGroup]=\"emailChangeForm\" (ngSubmit)=\"requestEmailChange()\">\n <div class=\"mb-3\">\n <label for=\"currentEmail\" class=\"form-label\">Current Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"currentEmail\"\n [value]=\"currentUser()?.email\"\n readonly>\n </div>\n <div class=\"mb-3\">\n <label for=\"newEmail\" class=\"form-label\">New Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"newEmail\"\n formControlName=\"newEmail\"\n [class.is-invalid]=\"emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched\">\n @if (emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched) {\n <div class=\"invalid-feedback\">\n Please enter a valid email address\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"passwordEmailChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"passwordEmailChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched\">\n @if (emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n\n @if (emailMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ emailMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"emailChangeForm.invalid || isChangingEmail()\">\n @if (isChangingEmail()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-envelope-check me-1\"></i>\n Request Email Change\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"profileNav\" class=\"mt-3\"></div>\n\n <!-- Error Messages -->\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n </div>\n }\n }\n </div>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i2$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }] });
|
|
3142
3182
|
}
|
|
3143
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3183
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserProfileComponent, decorators: [{
|
|
3144
3184
|
type: Component,
|
|
3145
3185
|
args: [{ selector: 'ccc-user-profile', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"user-profile-container\">\n <!-- Header Section -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <h4 class=\"mb-0\">\n <i class=\"bi bi-person-circle me-2 text-primary\"></i>User Profile\n </h4>\n @if (isStaff()) {\n <span class=\"badge bg-warning ms-3\">Staff</span>\n }\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"profile-content p-4\">\n @if (isLoading()) {\n <div class=\"text-center\">\n <div class=\"spinner-border text-primary me-2\"></div>\n <span>Loading profile...</span>\n </div>\n } @else {\n <!-- User Info Card -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>Account Information\n </h5>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Full Name:</small>\n <div class=\"fw-semibold\">{{ fullName() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Username:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.username }}</div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Email:</small>\n <div class=\"fw-semibold\">{{ currentUser()?.email }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Account Status:</small>\n <div>\n @if (currentUser()?.isActive) {\n <span class=\"badge bg-success\">Active</span>\n } @else {\n <span class=\"badge bg-danger\">Inactive</span>\n }\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Member Since:</small>\n <div class=\"fw-semibold\">{{ joinDate() }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Last Login:</small>\n <div class=\"fw-semibold\">{{ lastLogin() }}</div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Navigation Tabs -->\n <ul ngbNav #profileNav=\"ngbNav\" [activeId]=\"activeTab()\" (activeIdChange)=\"setActiveTab($event)\" class=\"nav-tabs\">\n <li [ngbNavItem]=\"'profile'\">\n <button ngbNavLink (click)=\"setActiveTab('profile')\">\n <i class=\"bi bi-person me-1\"></i>Profile Settings\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Profile Update Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-pencil-square me-2\"></i>Update Profile Information\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"profileForm\" (ngSubmit)=\"updateProfile()\">\n <div class=\"row g-3\">\n <div class=\"col-md-6\">\n <label for=\"firstName\" class=\"form-label\">First Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"firstName\"\n formControlName=\"firstName\"\n [class.is-invalid]=\"profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched\">\n @if (profileForm.get('firstName')?.invalid && profileForm.get('firstName')?.touched) {\n <div class=\"invalid-feedback\">\n First name is required (max 30 characters)\n </div>\n }\n </div>\n <div class=\"col-md-6\">\n <label for=\"lastName\" class=\"form-label\">Last Name</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"lastName\"\n formControlName=\"lastName\"\n [class.is-invalid]=\"profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched\">\n @if (profileForm.get('lastName')?.invalid && profileForm.get('lastName')?.touched) {\n <div class=\"invalid-feedback\">\n Last name is required (max 30 characters)\n </div>\n }\n </div>\n </div>\n <div class=\"mt-3\">\n <label for=\"currentPassword\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPassword\"\n formControlName=\"currentPassword\"\n placeholder=\"Enter your current password to confirm changes\"\n [class.is-invalid]=\"profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched\">\n @if (profileForm.get('currentPassword')?.invalid && profileForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required to update profile\n </div>\n }\n </div>\n\n @if (profileMessage()) {\n <div class=\"alert alert-success mt-3\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ profileMessage() }}\n </div>\n }\n\n <div class=\"mt-3\">\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"profileForm.invalid || isUpdatingProfile()\">\n @if (isUpdatingProfile()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n Update Profile\n </button>\n </div>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'password'\">\n <button ngbNavLink (click)=\"setActiveTab('password')\">\n <i class=\"bi bi-shield-lock me-1\"></i>Change Password\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Password Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-shield-lock me-2\"></i>Change Password\n </h6>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"passwordForm\" (ngSubmit)=\"changePassword()\">\n <div class=\"mb-3\">\n <label for=\"currentPasswordChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"currentPasswordChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched\">\n @if (passwordForm.get('currentPassword')?.invalid && passwordForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"newPassword\" class=\"form-label\">New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"newPassword\"\n formControlName=\"newPassword\"\n [class.is-invalid]=\"passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched\">\n @if (passwordForm.get('newPassword')?.invalid && passwordForm.get('newPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Password must be at least 8 characters long\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"confirmPassword\" class=\"form-label\">Confirm New Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"confirmPassword\"\n formControlName=\"confirmPassword\"\n [class.is-invalid]=\"(passwordForm.get('confirmPassword')?.invalid || passwordForm.hasError('passwordMismatch')) && passwordForm.get('confirmPassword')?.touched\">\n @if (passwordForm.get('confirmPassword')?.invalid && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Please confirm your new password\n </div>\n }\n @if (passwordForm.hasError('passwordMismatch') && passwordForm.get('confirmPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Passwords do not match\n </div>\n }\n </div>\n\n @if (passwordMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ passwordMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"passwordForm.invalid || isChangingPassword()\">\n @if (isChangingPassword()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-shield-check me-1\"></i>\n Change Password\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"'email'\">\n <button ngbNavLink (click)=\"setActiveTab('email')\">\n <i class=\"bi bi-envelope me-1\"></i>Change Email\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-4\">\n <!-- Email Change Form -->\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-envelope me-2\"></i>Change Email Address\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Changing your email will require verification. You'll receive a confirmation email at your new address.\n </div>\n \n <form [formGroup]=\"emailChangeForm\" (ngSubmit)=\"requestEmailChange()\">\n <div class=\"mb-3\">\n <label for=\"currentEmail\" class=\"form-label\">Current Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"currentEmail\"\n [value]=\"currentUser()?.email\"\n readonly>\n </div>\n <div class=\"mb-3\">\n <label for=\"newEmail\" class=\"form-label\">New Email</label>\n <input\n type=\"email\"\n class=\"form-control\"\n id=\"newEmail\"\n formControlName=\"newEmail\"\n [class.is-invalid]=\"emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched\">\n @if (emailChangeForm.get('newEmail')?.invalid && emailChangeForm.get('newEmail')?.touched) {\n <div class=\"invalid-feedback\">\n Please enter a valid email address\n </div>\n }\n </div>\n <div class=\"mb-3\">\n <label for=\"passwordEmailChange\" class=\"form-label\">Current Password</label>\n <input\n type=\"password\"\n class=\"form-control\"\n id=\"passwordEmailChange\"\n formControlName=\"currentPassword\"\n [class.is-invalid]=\"emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched\">\n @if (emailChangeForm.get('currentPassword')?.invalid && emailChangeForm.get('currentPassword')?.touched) {\n <div class=\"invalid-feedback\">\n Current password is required\n </div>\n }\n </div>\n\n @if (emailMessage()) {\n <div class=\"alert alert-success\">\n <i class=\"bi bi-check-circle me-2\"></i>{{ emailMessage() }}\n </div>\n }\n\n <button\n type=\"submit\"\n class=\"btn btn-primary\"\n [disabled]=\"emailChangeForm.invalid || isChangingEmail()\">\n @if (isChangingEmail()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-envelope-check me-1\"></i>\n Request Email Change\n </button>\n </form>\n </div>\n </div>\n </div>\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"profileNav\" class=\"mt-3\"></div>\n\n <!-- Error Messages -->\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ errorMessage() }}\n </div>\n }\n }\n </div>\n</div>" }]
|
|
3146
3186
|
}], ctorParameters: () => [] });
|
|
@@ -3297,10 +3337,10 @@ class SiteConfigComponent {
|
|
|
3297
3337
|
fileInput.value = '';
|
|
3298
3338
|
}
|
|
3299
3339
|
}
|
|
3300
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3301
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: SiteConfigComponent, isStandalone: true, selector: "ccc-site-config", ngImport: i0, template: "<div class=\"site-config-container\">\r\n <!-- Header Section -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100\">\r\n <div class=\"d-flex align-items-center\">\r\n <h4 class=\"navbar-brand m-0 fw-bold\">\r\n <i class=\"bi bi-gear me-2\"></i>Site Configuration\r\n </h4>\r\n </div>\r\n <div class=\"d-flex gap-2\">\r\n <button \r\n type=\"submit\" \r\n form=\"configForm\"\r\n class=\"btn btn-primary btn-sm\"\r\n [disabled]=\"configForm.invalid || loading()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-1\" aria-hidden=\"true\"></span>\r\n } @else {\r\n <i class=\"bi bi-check-lg me-1\"></i>\r\n }\r\n Save Configuration\r\n </button>\r\n \r\n <button \r\n type=\"button\" \r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"resetForm()\"\r\n [disabled]=\"loading()\">\r\n <i class=\"bi bi-arrow-clockwise me-1\"></i>\r\n Reset\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <div class=\"config-content p-4\">\r\n <div class=\"row\">\r\n <div class=\"col-lg-8\">\r\n <!-- Site Branding Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-brush me-2\"></i>Site Branding\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <!-- Success Alert -->\r\n @if (success()) {\r\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- Error Alert -->\r\n @if (error()) {\r\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\r\n </ngb-alert>\r\n }\r\n\r\n <form id=\"configForm\" [formGroup]=\"configForm\" (ngSubmit)=\"onSubmit()\">\r\n <!-- Site Name -->\r\n <div class=\"mb-3\">\r\n <label for=\"site_name\" class=\"form-label\">\r\n <i class=\"bi bi-building me-1\"></i>\r\n Site Name <span class=\"text-danger\">*</span>\r\n </label>\r\n <input \r\n type=\"text\" \r\n class=\"form-control\" \r\n id=\"site_name\"\r\n formControlName=\"siteName\"\r\n [class.is-invalid]=\"configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched\"\r\n placeholder=\"Enter your site name\">\r\n @if (configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('siteName')?.errors?.['required']) {\r\n <span>Site name is required</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['minlength']) {\r\n <span>Site name must be at least 1 character</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['maxlength']) {\r\n <span>Site name must be less than 255 characters</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n This will appear in the navigation bar and login page\r\n </div>\r\n </div>\r\n\r\n <!-- Logo URL -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_url\" class=\"form-label\">\r\n <i class=\"bi bi-image me-1\"></i>\r\n Logo URL\r\n </label>\r\n <input \r\n type=\"url\" \r\n class=\"form-control\" \r\n id=\"logo_url\"\r\n formControlName=\"logoUrl\"\r\n placeholder=\"https://example.com/logo.png\">\r\n <div class=\"form-text\">\r\n Optional: URL to your organization's logo\r\n </div>\r\n </div>\r\n\r\n <!-- Logo File Upload -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_file\" class=\"form-label\">\r\n <i class=\"bi bi-upload me-1\"></i>\r\n Upload Logo File\r\n </label>\r\n <input \r\n type=\"file\" \r\n class=\"form-control\" \r\n id=\"logo_file\"\r\n accept=\"image/*\"\r\n (change)=\"onLogoFileSelected($event)\">\r\n <div class=\"form-text\">\r\n Upload a logo file (overrides logo URL). Supported: JPEG, PNG, GIF, SVG. Max 5MB.\r\n </div>\r\n @if (selectedLogoFile()) {\r\n <div class=\"mt-2\">\r\n <div class=\"alert alert-info py-2\">\r\n <i class=\"bi bi-file-image me-1\"></i>\r\n Selected: {{ selectedLogoFile()?.name }}\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger ms-2\" (click)=\"clearLogoFile()\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Primary Color -->\r\n <div class=\"mb-3\">\r\n <label for=\"primary_color\" class=\"form-label\">\r\n <i class=\"bi bi-palette me-1\"></i>\r\n Primary Color\r\n </label>\r\n <div class=\"d-flex gap-2 align-items-center\">\r\n <input\r\n type=\"color\"\r\n id=\"primary_color\"\r\n class=\"form-control form-control-color\"\r\n formControlName=\"primaryColor\"\r\n style=\"width: 60px; height: 40px;\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n formControlName=\"primaryColor\"\r\n placeholder=\"#1976d2\"\r\n [class.is-invalid]=\"configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched\">\r\n </div>\r\n @if (configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('primaryColor')?.errors?.['pattern']) {\r\n <span>Please enter a valid hex color (e.g., #1976d2)</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n Main theme color for navigation and buttons\r\n </div>\r\n </div>\r\n\r\n <!-- Show Powered By -->\r\n <div class=\"mb-4\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"show_powered_by\"\r\n formControlName=\"showPoweredBy\">\r\n <label class=\"form-check-label\" for=\"show_powered_by\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Show \"Powered by CUPCAKE Vanilla\"\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Display a small footer credit (appears on hover in bottom-right corner)\r\n </div>\r\n </div>\r\n\r\n <!-- Authentication Settings Section -->\r\n <div class=\"mb-4\">\r\n <h6 class=\"text-muted border-bottom pb-2\">\r\n <i class=\"bi bi-shield-lock me-2\"></i>Authentication Settings\r\n </h6>\r\n \r\n <!-- Allow User Registration -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"allow_user_registration\"\r\n formControlName=\"allowUserRegistration\">\r\n <label class=\"form-check-label\" for=\"allow_user_registration\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Allow Public User Registration\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n When enabled, users can register new accounts without admin approval. \r\n The registration API endpoint will be accessible to the public.\r\n </div>\r\n </div>\r\n\r\n <!-- Enable ORCID Login -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"enable_orcid_login\"\r\n formControlName=\"enableOrcidLogin\">\r\n <label class=\"form-check-label\" for=\"enable_orcid_login\">\r\n <i class=\"bi bi-person-badge me-1\"></i>\r\n Enable ORCID OAuth Login\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Allow users to log in using their ORCID account. \r\n Requires ORCID OAuth configuration in server settings.\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </form>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-lg-4\">\r\n <!-- Preview Panel -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-eye me-2\"></i>Preview\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"preview-navbar mb-3\" \r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <div class=\"preview-nav\">\r\n <span class=\"preview-brand\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 20px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"preview-login-card\">\r\n <h6 class=\"text-center mb-3\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 24px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </h6>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-person-badge me-2\"></i>\r\n Login with ORCID\r\n </div>\r\n }\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-box-arrow-in-right me-2\"></i>\r\n Regular Login\r\n </div>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <div class=\"text-center mb-2\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Don't have an account? <a href=\"#\" class=\"text-decoration-none\">Register here</a>\r\n </small>\r\n </div>\r\n }\r\n <div class=\"text-center\">\r\n <small class=\"text-muted\">\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }} - Scientific Metadata Management\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3\">\r\n <h6 class=\"text-muted\">Authentication Status:</h6>\r\n <ul class=\"list-unstyled small text-muted mb-0\">\r\n <li>\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Regular login: Always enabled\r\n </li>\r\n <li>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n ORCID login: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n ORCID login: Disabled\r\n }\r\n </li>\r\n <li>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Public registration: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n Public registration: Disabled\r\n }\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n @if (configForm.get('showPoweredBy')?.value) {\r\n <div class=\"mt-3\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Footer: \"Powered by CUPCAKE Vanilla\" will appear on hover\r\n </small>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: NgbAlert, selector: "ngb-alert", inputs: ["animation", "dismissible", "type"], outputs: ["closed"], exportAs: ["ngbAlert"] }] });
|
|
3340
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SiteConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3341
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SiteConfigComponent, isStandalone: true, selector: "ccc-site-config", ngImport: i0, template: "<div class=\"site-config-container\">\r\n <!-- Header Section -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100\">\r\n <div class=\"d-flex align-items-center\">\r\n <h4 class=\"navbar-brand m-0 fw-bold\">\r\n <i class=\"bi bi-gear me-2\"></i>Site Configuration\r\n </h4>\r\n </div>\r\n <div class=\"d-flex gap-2\">\r\n <button \r\n type=\"submit\" \r\n form=\"configForm\"\r\n class=\"btn btn-primary btn-sm\"\r\n [disabled]=\"configForm.invalid || loading()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-1\" aria-hidden=\"true\"></span>\r\n } @else {\r\n <i class=\"bi bi-check-lg me-1\"></i>\r\n }\r\n Save Configuration\r\n </button>\r\n \r\n <button \r\n type=\"button\" \r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"resetForm()\"\r\n [disabled]=\"loading()\">\r\n <i class=\"bi bi-arrow-clockwise me-1\"></i>\r\n Reset\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <div class=\"config-content p-4\">\r\n <div class=\"row\">\r\n <div class=\"col-lg-8\">\r\n <!-- Site Branding Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-brush me-2\"></i>Site Branding\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <!-- Success Alert -->\r\n @if (success()) {\r\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- Error Alert -->\r\n @if (error()) {\r\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\r\n </ngb-alert>\r\n }\r\n\r\n <form id=\"configForm\" [formGroup]=\"configForm\" (ngSubmit)=\"onSubmit()\">\r\n <!-- Site Name -->\r\n <div class=\"mb-3\">\r\n <label for=\"site_name\" class=\"form-label\">\r\n <i class=\"bi bi-building me-1\"></i>\r\n Site Name <span class=\"text-danger\">*</span>\r\n </label>\r\n <input \r\n type=\"text\" \r\n class=\"form-control\" \r\n id=\"site_name\"\r\n formControlName=\"siteName\"\r\n [class.is-invalid]=\"configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched\"\r\n placeholder=\"Enter your site name\">\r\n @if (configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('siteName')?.errors?.['required']) {\r\n <span>Site name is required</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['minlength']) {\r\n <span>Site name must be at least 1 character</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['maxlength']) {\r\n <span>Site name must be less than 255 characters</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n This will appear in the navigation bar and login page\r\n </div>\r\n </div>\r\n\r\n <!-- Logo URL -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_url\" class=\"form-label\">\r\n <i class=\"bi bi-image me-1\"></i>\r\n Logo URL\r\n </label>\r\n <input \r\n type=\"url\" \r\n class=\"form-control\" \r\n id=\"logo_url\"\r\n formControlName=\"logoUrl\"\r\n placeholder=\"https://example.com/logo.png\">\r\n <div class=\"form-text\">\r\n Optional: URL to your organization's logo\r\n </div>\r\n </div>\r\n\r\n <!-- Logo File Upload -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_file\" class=\"form-label\">\r\n <i class=\"bi bi-upload me-1\"></i>\r\n Upload Logo File\r\n </label>\r\n <input \r\n type=\"file\" \r\n class=\"form-control\" \r\n id=\"logo_file\"\r\n accept=\"image/*\"\r\n (change)=\"onLogoFileSelected($event)\">\r\n <div class=\"form-text\">\r\n Upload a logo file (overrides logo URL). Supported: JPEG, PNG, GIF, SVG. Max 5MB.\r\n </div>\r\n @if (selectedLogoFile()) {\r\n <div class=\"mt-2\">\r\n <div class=\"alert alert-info py-2\">\r\n <i class=\"bi bi-file-image me-1\"></i>\r\n Selected: {{ selectedLogoFile()?.name }}\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger ms-2\" (click)=\"clearLogoFile()\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Primary Color -->\r\n <div class=\"mb-3\">\r\n <label for=\"primary_color\" class=\"form-label\">\r\n <i class=\"bi bi-palette me-1\"></i>\r\n Primary Color\r\n </label>\r\n <div class=\"d-flex gap-2 align-items-center\">\r\n <input\r\n type=\"color\"\r\n id=\"primary_color\"\r\n class=\"form-control form-control-color\"\r\n formControlName=\"primaryColor\"\r\n style=\"width: 60px; height: 40px;\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n formControlName=\"primaryColor\"\r\n placeholder=\"#1976d2\"\r\n [class.is-invalid]=\"configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched\">\r\n </div>\r\n @if (configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('primaryColor')?.errors?.['pattern']) {\r\n <span>Please enter a valid hex color (e.g., #1976d2)</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n Main theme color for navigation and buttons\r\n </div>\r\n </div>\r\n\r\n <!-- Show Powered By -->\r\n <div class=\"mb-4\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"show_powered_by\"\r\n formControlName=\"showPoweredBy\">\r\n <label class=\"form-check-label\" for=\"show_powered_by\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Show \"Powered by CUPCAKE Vanilla\"\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Display a small footer credit (appears on hover in bottom-right corner)\r\n </div>\r\n </div>\r\n\r\n <!-- Authentication Settings Section -->\r\n <div class=\"mb-4\">\r\n <h6 class=\"text-muted border-bottom pb-2\">\r\n <i class=\"bi bi-shield-lock me-2\"></i>Authentication Settings\r\n </h6>\r\n \r\n <!-- Allow User Registration -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"allow_user_registration\"\r\n formControlName=\"allowUserRegistration\">\r\n <label class=\"form-check-label\" for=\"allow_user_registration\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Allow Public User Registration\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n When enabled, users can register new accounts without admin approval. \r\n The registration API endpoint will be accessible to the public.\r\n </div>\r\n </div>\r\n\r\n <!-- Enable ORCID Login -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"enable_orcid_login\"\r\n formControlName=\"enableOrcidLogin\">\r\n <label class=\"form-check-label\" for=\"enable_orcid_login\">\r\n <i class=\"bi bi-person-badge me-1\"></i>\r\n Enable ORCID OAuth Login\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Allow users to log in using their ORCID account. \r\n Requires ORCID OAuth configuration in server settings.\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </form>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-lg-4\">\r\n <!-- Preview Panel -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-eye me-2\"></i>Preview\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"preview-navbar mb-3\" \r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <div class=\"preview-nav\">\r\n <span class=\"preview-brand\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 20px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"preview-login-card\">\r\n <h6 class=\"text-center mb-3\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 24px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </h6>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-person-badge me-2\"></i>\r\n Login with ORCID\r\n </div>\r\n }\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-box-arrow-in-right me-2\"></i>\r\n Regular Login\r\n </div>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <div class=\"text-center mb-2\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Don't have an account? <a href=\"#\" class=\"text-decoration-none\">Register here</a>\r\n </small>\r\n </div>\r\n }\r\n <div class=\"text-center\">\r\n <small class=\"text-muted\">\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }} - Scientific Metadata Management\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3\">\r\n <h6 class=\"text-muted\">Authentication Status:</h6>\r\n <ul class=\"list-unstyled small text-muted mb-0\">\r\n <li>\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Regular login: Always enabled\r\n </li>\r\n <li>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n ORCID login: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n ORCID login: Disabled\r\n }\r\n </li>\r\n <li>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Public registration: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n Public registration: Disabled\r\n }\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n @if (configForm.get('showPoweredBy')?.value) {\r\n <div class=\"mt-3\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Footer: \"Powered by CUPCAKE Vanilla\" will appear on hover\r\n </small>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: NgbAlert, selector: "ngb-alert", inputs: ["animation", "dismissible", "type"], outputs: ["closed"], exportAs: ["ngbAlert"] }] });
|
|
3302
3342
|
}
|
|
3303
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3343
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SiteConfigComponent, decorators: [{
|
|
3304
3344
|
type: Component,
|
|
3305
3345
|
args: [{ selector: 'ccc-site-config', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbAlert], template: "<div class=\"site-config-container\">\r\n <!-- Header Section -->\r\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\r\n <div class=\"container-fluid\">\r\n <div class=\"d-flex align-items-center justify-content-between w-100\">\r\n <div class=\"d-flex align-items-center\">\r\n <h4 class=\"navbar-brand m-0 fw-bold\">\r\n <i class=\"bi bi-gear me-2\"></i>Site Configuration\r\n </h4>\r\n </div>\r\n <div class=\"d-flex gap-2\">\r\n <button \r\n type=\"submit\" \r\n form=\"configForm\"\r\n class=\"btn btn-primary btn-sm\"\r\n [disabled]=\"configForm.invalid || loading()\">\r\n @if (loading()) {\r\n <span class=\"spinner-border spinner-border-sm me-1\" aria-hidden=\"true\"></span>\r\n } @else {\r\n <i class=\"bi bi-check-lg me-1\"></i>\r\n }\r\n Save Configuration\r\n </button>\r\n \r\n <button \r\n type=\"button\" \r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"resetForm()\"\r\n [disabled]=\"loading()\">\r\n <i class=\"bi bi-arrow-clockwise me-1\"></i>\r\n Reset\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <div class=\"config-content p-4\">\r\n <div class=\"row\">\r\n <div class=\"col-lg-8\">\r\n <!-- Site Branding Card -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-brush me-2\"></i>Site Branding\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <!-- Success Alert -->\r\n @if (success()) {\r\n <ngb-alert type=\"success\" (closed)=\"clearSuccess()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-check-circle me-2\"></i>{{ success() }}\r\n </ngb-alert>\r\n }\r\n\r\n <!-- Error Alert -->\r\n @if (error()) {\r\n <ngb-alert type=\"danger\" (closed)=\"clearError()\" [dismissible]=\"true\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error() }}\r\n </ngb-alert>\r\n }\r\n\r\n <form id=\"configForm\" [formGroup]=\"configForm\" (ngSubmit)=\"onSubmit()\">\r\n <!-- Site Name -->\r\n <div class=\"mb-3\">\r\n <label for=\"site_name\" class=\"form-label\">\r\n <i class=\"bi bi-building me-1\"></i>\r\n Site Name <span class=\"text-danger\">*</span>\r\n </label>\r\n <input \r\n type=\"text\" \r\n class=\"form-control\" \r\n id=\"site_name\"\r\n formControlName=\"siteName\"\r\n [class.is-invalid]=\"configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched\"\r\n placeholder=\"Enter your site name\">\r\n @if (configForm.get('siteName')?.invalid && configForm.get('siteName')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('siteName')?.errors?.['required']) {\r\n <span>Site name is required</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['minlength']) {\r\n <span>Site name must be at least 1 character</span>\r\n }\r\n @if (configForm.get('siteName')?.errors?.['maxlength']) {\r\n <span>Site name must be less than 255 characters</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n This will appear in the navigation bar and login page\r\n </div>\r\n </div>\r\n\r\n <!-- Logo URL -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_url\" class=\"form-label\">\r\n <i class=\"bi bi-image me-1\"></i>\r\n Logo URL\r\n </label>\r\n <input \r\n type=\"url\" \r\n class=\"form-control\" \r\n id=\"logo_url\"\r\n formControlName=\"logoUrl\"\r\n placeholder=\"https://example.com/logo.png\">\r\n <div class=\"form-text\">\r\n Optional: URL to your organization's logo\r\n </div>\r\n </div>\r\n\r\n <!-- Logo File Upload -->\r\n <div class=\"mb-3\">\r\n <label for=\"logo_file\" class=\"form-label\">\r\n <i class=\"bi bi-upload me-1\"></i>\r\n Upload Logo File\r\n </label>\r\n <input \r\n type=\"file\" \r\n class=\"form-control\" \r\n id=\"logo_file\"\r\n accept=\"image/*\"\r\n (change)=\"onLogoFileSelected($event)\">\r\n <div class=\"form-text\">\r\n Upload a logo file (overrides logo URL). Supported: JPEG, PNG, GIF, SVG. Max 5MB.\r\n </div>\r\n @if (selectedLogoFile()) {\r\n <div class=\"mt-2\">\r\n <div class=\"alert alert-info py-2\">\r\n <i class=\"bi bi-file-image me-1\"></i>\r\n Selected: {{ selectedLogoFile()?.name }}\r\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger ms-2\" (click)=\"clearLogoFile()\">\r\n <i class=\"bi bi-x\"></i>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Primary Color -->\r\n <div class=\"mb-3\">\r\n <label for=\"primary_color\" class=\"form-label\">\r\n <i class=\"bi bi-palette me-1\"></i>\r\n Primary Color\r\n </label>\r\n <div class=\"d-flex gap-2 align-items-center\">\r\n <input\r\n type=\"color\"\r\n id=\"primary_color\"\r\n class=\"form-control form-control-color\"\r\n formControlName=\"primaryColor\"\r\n style=\"width: 60px; height: 40px;\">\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n formControlName=\"primaryColor\"\r\n placeholder=\"#1976d2\"\r\n [class.is-invalid]=\"configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched\">\r\n </div>\r\n @if (configForm.get('primaryColor')?.invalid && configForm.get('primaryColor')?.touched) {\r\n <div class=\"invalid-feedback\">\r\n @if (configForm.get('primaryColor')?.errors?.['pattern']) {\r\n <span>Please enter a valid hex color (e.g., #1976d2)</span>\r\n }\r\n </div>\r\n }\r\n <div class=\"form-text\">\r\n Main theme color for navigation and buttons\r\n </div>\r\n </div>\r\n\r\n <!-- Show Powered By -->\r\n <div class=\"mb-4\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"show_powered_by\"\r\n formControlName=\"showPoweredBy\">\r\n <label class=\"form-check-label\" for=\"show_powered_by\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Show \"Powered by CUPCAKE Vanilla\"\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Display a small footer credit (appears on hover in bottom-right corner)\r\n </div>\r\n </div>\r\n\r\n <!-- Authentication Settings Section -->\r\n <div class=\"mb-4\">\r\n <h6 class=\"text-muted border-bottom pb-2\">\r\n <i class=\"bi bi-shield-lock me-2\"></i>Authentication Settings\r\n </h6>\r\n \r\n <!-- Allow User Registration -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"allow_user_registration\"\r\n formControlName=\"allowUserRegistration\">\r\n <label class=\"form-check-label\" for=\"allow_user_registration\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Allow Public User Registration\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n When enabled, users can register new accounts without admin approval. \r\n The registration API endpoint will be accessible to the public.\r\n </div>\r\n </div>\r\n\r\n <!-- Enable ORCID Login -->\r\n <div class=\"mb-3\">\r\n <div class=\"form-check\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"enable_orcid_login\"\r\n formControlName=\"enableOrcidLogin\">\r\n <label class=\"form-check-label\" for=\"enable_orcid_login\">\r\n <i class=\"bi bi-person-badge me-1\"></i>\r\n Enable ORCID OAuth Login\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n Allow users to log in using their ORCID account. \r\n Requires ORCID OAuth configuration in server settings.\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </form>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n\r\n <div class=\"col-lg-4\">\r\n <!-- Preview Panel -->\r\n <div class=\"card mb-4\">\r\n <div class=\"card-header\">\r\n <h5 class=\"mb-0\">\r\n <i class=\"bi bi-eye me-2\"></i>Preview\r\n </h5>\r\n </div>\r\n <div class=\"card-body\">\r\n <div class=\"preview-navbar mb-3\" \r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <div class=\"preview-nav\">\r\n <span class=\"preview-brand\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 20px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"preview-login-card\">\r\n <h6 class=\"text-center mb-3\">\r\n @if (configForm.get('logoUrl')?.value) {\r\n <img [src]=\"configForm.get('logoUrl')?.value\" alt=\"Logo\" style=\"height: 24px; width: auto;\" class=\"me-2\">\r\n } @else {\r\n <i class=\"bi bi-flask me-2\"></i>\r\n }\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }}\r\n </h6>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-person-badge me-2\"></i>\r\n Login with ORCID\r\n </div>\r\n }\r\n <div class=\"preview-button mb-2\"\r\n [style.background]=\"'linear-gradient(135deg, ' + (configForm.get('primaryColor')?.value || '#1976d2') + ' 0%, ' + getDarkerColor(configForm.get('primaryColor')?.value || '#1976d2') + ' 100%)'\">\r\n <i class=\"bi bi-box-arrow-in-right me-2\"></i>\r\n Regular Login\r\n </div>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <div class=\"text-center mb-2\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-person-plus me-1\"></i>\r\n Don't have an account? <a href=\"#\" class=\"text-decoration-none\">Register here</a>\r\n </small>\r\n </div>\r\n }\r\n <div class=\"text-center\">\r\n <small class=\"text-muted\">\r\n {{ configForm.get('siteName')?.value || 'Your Site Name' }} - Scientific Metadata Management\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"mt-3\">\r\n <h6 class=\"text-muted\">Authentication Status:</h6>\r\n <ul class=\"list-unstyled small text-muted mb-0\">\r\n <li>\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Regular login: Always enabled\r\n </li>\r\n <li>\r\n @if (configForm.get('enableOrcidLogin')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n ORCID login: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n ORCID login: Disabled\r\n }\r\n </li>\r\n <li>\r\n @if (configForm.get('allowUserRegistration')?.value) {\r\n <i class=\"bi bi-check-circle-fill text-success me-1\"></i>\r\n Public registration: Enabled\r\n } @else {\r\n <i class=\"bi bi-x-circle-fill text-danger me-1\"></i>\r\n Public registration: Disabled\r\n }\r\n </li>\r\n </ul>\r\n </div>\r\n\r\n @if (configForm.get('showPoweredBy')?.value) {\r\n <div class=\"mt-3\">\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Footer: \"Powered by CUPCAKE Vanilla\" will appear on hover\r\n </small>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n</div>" }]
|
|
3306
3346
|
}], ctorParameters: () => [] });
|
|
@@ -3344,10 +3384,10 @@ class ToastContainerComponent {
|
|
|
3344
3384
|
return 'bi-info-circle-fill';
|
|
3345
3385
|
}
|
|
3346
3386
|
}
|
|
3347
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3348
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3387
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3388
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ToastContainerComponent, isStandalone: true, selector: "ccc-toast-container", ngImport: i0, template: "<div class=\"toast-container position-fixed bottom-0 end-0 p-3\" style=\"z-index: 9999;\">\n @for (toast of toasts(); track toast.id) {\n <ngb-toast \n [class]=\"getToastClass(toast.type)\"\n [autohide]=\"true\"\n [delay]=\"toast.duration || 5000\"\n (hidden)=\"remove(toast)\">\n <ng-template ngbToastHeader>\n <i [class]=\"'bi ' + getToastIcon(toast.type) + ' me-2'\"></i>\n <strong class=\"me-auto\">\n {{ toast.type | titlecase }}\n </strong>\n </ng-template>\n {{ toast.message }}\n </ngb-toast>\n }\n</div>\n", styles: [".toast-container{max-width:400px}.toast-container .toast{margin-bottom:.5rem;border:none;box-shadow:0 .5rem 1rem #00000026}.toast-container .toast .toast-header{border-bottom:1px solid rgba(255,255,255,.1)}.toast-container .toast .toast-header .btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast-container .toast .toast-body{font-size:.875rem}[data-bs-theme=dark] .toast-container .toast.bg-warning{color:var(--bs-dark)!important}[data-bs-theme=dark] .toast-container .toast.bg-warning .toast-header{border-bottom-color:#0000001a}[data-bs-theme=dark] .toast-container .toast.bg-warning .toast-header .btn-close{filter:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2$1.NgbToast, selector: "ngb-toast", inputs: ["animation", "delay", "autohide", "header"], outputs: ["shown", "hidden"], exportAs: ["ngbToast"] }, { kind: "directive", type: i2$1.NgbToastHeader, selector: "[ngbToastHeader]" }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }] });
|
|
3349
3389
|
}
|
|
3350
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3390
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastContainerComponent, decorators: [{
|
|
3351
3391
|
type: Component,
|
|
3352
3392
|
args: [{ selector: 'ccc-toast-container', standalone: true, imports: [CommonModule, NgbModule], template: "<div class=\"toast-container position-fixed bottom-0 end-0 p-3\" style=\"z-index: 9999;\">\n @for (toast of toasts(); track toast.id) {\n <ngb-toast \n [class]=\"getToastClass(toast.type)\"\n [autohide]=\"true\"\n [delay]=\"toast.duration || 5000\"\n (hidden)=\"remove(toast)\">\n <ng-template ngbToastHeader>\n <i [class]=\"'bi ' + getToastIcon(toast.type) + ' me-2'\"></i>\n <strong class=\"me-auto\">\n {{ toast.type | titlecase }}\n </strong>\n </ng-template>\n {{ toast.message }}\n </ngb-toast>\n }\n</div>\n", styles: [".toast-container{max-width:400px}.toast-container .toast{margin-bottom:.5rem;border:none;box-shadow:0 .5rem 1rem #00000026}.toast-container .toast .toast-header{border-bottom:1px solid rgba(255,255,255,.1)}.toast-container .toast .toast-header .btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast-container .toast .toast-body{font-size:.875rem}[data-bs-theme=dark] .toast-container .toast.bg-warning{color:var(--bs-dark)!important}[data-bs-theme=dark] .toast-container .toast.bg-warning .toast-header{border-bottom-color:#0000001a}[data-bs-theme=dark] .toast-container .toast.bg-warning .toast-header .btn-close{filter:none}\n"] }]
|
|
3353
3393
|
}], ctorParameters: () => [] });
|
|
@@ -3355,10 +3395,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
3355
3395
|
class PoweredByFooterComponent {
|
|
3356
3396
|
siteConfigService = inject(SiteConfigService);
|
|
3357
3397
|
siteConfig$ = this.siteConfigService.config$;
|
|
3358
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3359
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.
|
|
3398
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PoweredByFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3399
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: PoweredByFooterComponent, isStandalone: true, selector: "ccc-powered-by-footer", ngImport: i0, template: "@if ((siteConfig$ | async)?.showPoweredBy !== false) {\r\n <div class=\"powered-by-footer\">\r\n <div class=\"footer-trigger\"></div>\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Powered by <strong>CUPCAKE Vanilla</strong>\r\n </span>\r\n </div>\r\n </div>\r\n}", styles: [".powered-by-footer{position:fixed;bottom:0;right:20px;z-index:1000;pointer-events:none}.footer-trigger{position:absolute;bottom:0;right:0;width:60px;height:20px;pointer-events:all}.footer-content{position:absolute;bottom:5px;right:0;background:#000c;backdrop-filter:blur(8px);color:#fff;padding:6px 12px;border-radius:20px;font-size:.75rem;font-weight:500;white-space:nowrap;opacity:0;transform:translateY(10px) scale(.95);transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;box-shadow:0 4px 12px #00000026}.dark-mode .footer-content,:root[data-bs-theme=dark] .footer-content{background:#ffffffe6;color:var(--cupcake-text)}.footer-trigger:hover+.footer-content,.footer-content:hover{opacity:1;transform:translateY(0) scale(1);pointer-events:all}.footer-text{display:flex;align-items:center;color:currentColor}.footer-text strong{background:linear-gradient(135deg,var(--cupcake-primary),var(--cupcake-primary-dark));background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:600}.dark-mode .footer-text strong,:root[data-bs-theme=dark] .footer-text strong{-webkit-text-fill-color:var(--cupcake-primary);background:none;color:var(--cupcake-primary)}.footer-text i{color:#ffc107;font-size:.8rem}.dark-mode .footer-text i,:root[data-bs-theme=dark] .footer-text i{color:#ffeb3b}.powered-by-footer{animation:slideInFromBottom .5s ease-out 2s both}@keyframes slideInFromBottom{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@media (max-width: 768px){.powered-by-footer{display:none}}.powered-by-footer{user-select:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] });
|
|
3360
3400
|
}
|
|
3361
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3401
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: PoweredByFooterComponent, decorators: [{
|
|
3362
3402
|
type: Component,
|
|
3363
3403
|
args: [{ selector: 'ccc-powered-by-footer', standalone: true, imports: [CommonModule], template: "@if ((siteConfig$ | async)?.showPoweredBy !== false) {\r\n <div class=\"powered-by-footer\">\r\n <div class=\"footer-trigger\"></div>\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n <i class=\"bi bi-lightning-charge me-1\"></i>\r\n Powered by <strong>CUPCAKE Vanilla</strong>\r\n </span>\r\n </div>\r\n </div>\r\n}", styles: [".powered-by-footer{position:fixed;bottom:0;right:20px;z-index:1000;pointer-events:none}.footer-trigger{position:absolute;bottom:0;right:0;width:60px;height:20px;pointer-events:all}.footer-content{position:absolute;bottom:5px;right:0;background:#000c;backdrop-filter:blur(8px);color:#fff;padding:6px 12px;border-radius:20px;font-size:.75rem;font-weight:500;white-space:nowrap;opacity:0;transform:translateY(10px) scale(.95);transition:all .3s cubic-bezier(.4,0,.2,1);pointer-events:none;box-shadow:0 4px 12px #00000026}.dark-mode .footer-content,:root[data-bs-theme=dark] .footer-content{background:#ffffffe6;color:var(--cupcake-text)}.footer-trigger:hover+.footer-content,.footer-content:hover{opacity:1;transform:translateY(0) scale(1);pointer-events:all}.footer-text{display:flex;align-items:center;color:currentColor}.footer-text strong{background:linear-gradient(135deg,var(--cupcake-primary),var(--cupcake-primary-dark));background-clip:text;-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-weight:600}.dark-mode .footer-text strong,:root[data-bs-theme=dark] .footer-text strong{-webkit-text-fill-color:var(--cupcake-primary);background:none;color:var(--cupcake-primary)}.footer-text i{color:#ffc107;font-size:.8rem}.dark-mode .footer-text i,:root[data-bs-theme=dark] .footer-text i{color:#ffeb3b}.powered-by-footer{animation:slideInFromBottom .5s ease-out 2s both}@keyframes slideInFromBottom{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@media (max-width: 768px){.powered-by-footer{display:none}}.powered-by-footer{user-select:none}\n"] }]
|
|
3364
3404
|
}] });
|
|
@@ -3375,8 +3415,8 @@ class CupcakeCoreModule {
|
|
|
3375
3415
|
]
|
|
3376
3416
|
};
|
|
3377
3417
|
}
|
|
3378
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
3379
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.
|
|
3418
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeCoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
3419
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.7", ngImport: i0, type: CupcakeCoreModule, imports: [CommonModule,
|
|
3380
3420
|
ReactiveFormsModule,
|
|
3381
3421
|
HttpClientModule,
|
|
3382
3422
|
RouterModule,
|
|
@@ -3389,7 +3429,7 @@ class CupcakeCoreModule {
|
|
|
3389
3429
|
CommonModule,
|
|
3390
3430
|
ReactiveFormsModule,
|
|
3391
3431
|
NgbModule] });
|
|
3392
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.
|
|
3432
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeCoreModule, imports: [CommonModule,
|
|
3393
3433
|
ReactiveFormsModule,
|
|
3394
3434
|
HttpClientModule,
|
|
3395
3435
|
RouterModule,
|
|
@@ -3400,7 +3440,7 @@ class CupcakeCoreModule {
|
|
|
3400
3440
|
ReactiveFormsModule,
|
|
3401
3441
|
NgbModule] });
|
|
3402
3442
|
}
|
|
3403
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
3443
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeCoreModule, decorators: [{
|
|
3404
3444
|
type: NgModule,
|
|
3405
3445
|
args: [{
|
|
3406
3446
|
declarations: [],
|