@fhss-web-team/frontend-utils 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/fhss-web-team-frontend-utils.mjs +168 -67
- package/fesm2022/fhss-web-team-frontend-utils.mjs.map +1 -1
- package/lib/components/user-management/user-management.component.d.ts +1 -1
- package/lib/components/user-management/user-management.service.d.ts +1 -1
- package/lib/services/auth/auth.service.d.ts +7 -5
- package/lib/signals/fetch-signal/fetch-signal.d.ts +2 -350
- package/lib/signals/fetch-signal/fetch-signal.types.d.ts +460 -0
- package/lib/signals/trpcResource/trpcResource.d.ts +11 -0
- package/lib/signals/trpcResource/trpcResource.types.d.ts +65 -0
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Component, InjectionToken, inject, computed, Injectable, input,
|
|
2
|
+
import { Component, InjectionToken, inject, Injector, signal, afterNextRender, effect, computed, Injectable, input, untracked, ViewChild } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/router';
|
|
4
4
|
import { Router, RouterModule, RedirectCommand } from '@angular/router';
|
|
5
|
-
import { KEYCLOAK_EVENT_SIGNAL } from 'keycloak-angular';
|
|
5
|
+
import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType, typeEventArgs } from 'keycloak-angular';
|
|
6
6
|
import Keycloak from 'keycloak-js';
|
|
7
7
|
import * as i1$1 from '@angular/material/table';
|
|
8
8
|
import { MatTableModule } from '@angular/material/table';
|
|
@@ -19,6 +19,7 @@ import * as i6 from '@angular/material/select';
|
|
|
19
19
|
import { MatSelectModule } from '@angular/material/select';
|
|
20
20
|
import * as i1$2 from '@angular/material/button';
|
|
21
21
|
import { MatButtonModule } from '@angular/material/button';
|
|
22
|
+
import { isTRPCClientError } from '@trpc/client';
|
|
22
23
|
|
|
23
24
|
class ByuFooterComponent {
|
|
24
25
|
currentYear = new Date().getFullYear(); // Automatically updates the year
|
|
@@ -42,6 +43,25 @@ class AuthService {
|
|
|
42
43
|
router = inject(Router);
|
|
43
44
|
config = inject(FHSS_CONFIG);
|
|
44
45
|
nextUriKey = 'nextUri';
|
|
46
|
+
injector = inject(Injector);
|
|
47
|
+
/**
|
|
48
|
+
* Signal indicating whether the user is authenticated.
|
|
49
|
+
* Returns `true` if authenticated, `false` otherwise.
|
|
50
|
+
*/
|
|
51
|
+
authenticated = signal(false);
|
|
52
|
+
constructor() {
|
|
53
|
+
afterNextRender(() => {
|
|
54
|
+
effect(() => {
|
|
55
|
+
const kcEvent = this.keycloakSignal();
|
|
56
|
+
if (kcEvent.type === KeycloakEventType.Ready) {
|
|
57
|
+
this.authenticated.set(typeEventArgs(kcEvent.args));
|
|
58
|
+
}
|
|
59
|
+
if (kcEvent.type === KeycloakEventType.AuthLogout) {
|
|
60
|
+
this.authenticated.set(false);
|
|
61
|
+
}
|
|
62
|
+
}, { injector: this.injector });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
45
65
|
/**
|
|
46
66
|
* Initiates the login process. Optionally stores a route to redirect to after login.
|
|
47
67
|
* @param nextUri - The route to navigate to after successful login.
|
|
@@ -84,7 +104,7 @@ class AuthService {
|
|
|
84
104
|
this.router.navigate(['/']);
|
|
85
105
|
return;
|
|
86
106
|
}
|
|
87
|
-
const res = await fetch('/api/auth/callback', {
|
|
107
|
+
const res = await fetch(window.location.origin + '/api/auth/callback', {
|
|
88
108
|
headers: { Authorization: this.bearerToken() ?? '' },
|
|
89
109
|
});
|
|
90
110
|
if (!res.ok)
|
|
@@ -105,14 +125,6 @@ class AuthService {
|
|
|
105
125
|
logout = (nextUri) => this.keycloak.logout({
|
|
106
126
|
redirectUri: window.location.origin + (nextUri ?? '/'),
|
|
107
127
|
});
|
|
108
|
-
/**
|
|
109
|
-
* Signal indicating whether the user is authenticated.
|
|
110
|
-
* Returns `true` if authenticated, `false` otherwise.
|
|
111
|
-
*/
|
|
112
|
-
authenticated = computed(() => {
|
|
113
|
-
this.keycloakSignal();
|
|
114
|
-
return this.keycloak.authenticated;
|
|
115
|
-
});
|
|
116
128
|
/**
|
|
117
129
|
* Signal for the base64-encoded token used in the `Authorization` header.
|
|
118
130
|
*/
|
|
@@ -216,7 +228,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImpo
|
|
|
216
228
|
args: [{
|
|
217
229
|
providedIn: 'root',
|
|
218
230
|
}]
|
|
219
|
-
}] });
|
|
231
|
+
}], ctorParameters: () => [] });
|
|
220
232
|
|
|
221
233
|
class ByuHeaderComponent {
|
|
222
234
|
auth = inject(AuthService);
|
|
@@ -260,42 +272,43 @@ const debounce = (callback, wait) => {
|
|
|
260
272
|
};
|
|
261
273
|
};
|
|
262
274
|
|
|
263
|
-
/**
|
|
264
|
-
* Reads the underlying value from a MaybeSignal.
|
|
265
|
-
*
|
|
266
|
-
* This function is designed for use with reactive APIs (like fetchSignal) where it's important
|
|
267
|
-
* that Angular's computed methods register the dependency. If the input is a Signal, invoking it
|
|
268
|
-
* not only returns its current value but also tracks the signal for reactivity. If the input is
|
|
269
|
-
* a plain value, it simply returns that value.
|
|
270
|
-
*
|
|
271
|
-
* @template T The type of the value.
|
|
272
|
-
* @param value A plain value or a Signal that yields the value.
|
|
273
|
-
* @returns The current value, with dependency tracking enabled if the input is a Signal.
|
|
274
|
-
*/
|
|
275
|
-
function readMaybeSignal(value) {
|
|
276
|
-
return typeof value === 'function' ? value() : value;
|
|
277
|
-
}
|
|
278
275
|
/**
|
|
279
276
|
* Creates a reactive fetch signal.
|
|
280
277
|
*
|
|
281
278
|
* The request function can include Signals for properties. These are unwrapped in the refresh function.
|
|
282
279
|
*/
|
|
283
|
-
function createFetchSignal(request, method, transform,
|
|
280
|
+
function createFetchSignal(request, method, transform, options) {
|
|
284
281
|
// Use a computed signal so that any changes to Signals in the request object trigger updates.
|
|
285
282
|
const currentRequest = computed(request);
|
|
286
|
-
const value = signal(
|
|
287
|
-
const
|
|
283
|
+
const value = signal(options?.defaultValue, { equal: options?.equal });
|
|
284
|
+
const errorResponse = signal(undefined);
|
|
288
285
|
const isLoading = signal(false);
|
|
289
|
-
const error = signal(undefined);
|
|
290
286
|
const statusCode = signal(undefined);
|
|
291
287
|
const headers = signal(undefined);
|
|
288
|
+
const status = signal('idle');
|
|
289
|
+
const error = signal(undefined);
|
|
290
|
+
const injector = inject(Injector);
|
|
291
|
+
let effectRef = undefined;
|
|
292
|
+
if (options?.autoRefresh) {
|
|
293
|
+
effectRef = effect((onCleanup) => {
|
|
294
|
+
// pass abort signal to refresh on cleanup of effect
|
|
295
|
+
const controller = new AbortController();
|
|
296
|
+
onCleanup(() => controller.abort());
|
|
297
|
+
// call refresh with this abort controller
|
|
298
|
+
refresh(controller.signal, true);
|
|
299
|
+
}, { injector: options?.injector || injector });
|
|
300
|
+
}
|
|
292
301
|
const refresh = async (abortSignal, keepLoadingThroughAbort) => {
|
|
302
|
+
// if the fetchSignal has been destroyed, do nothing
|
|
303
|
+
if (untracked(status) === 'destroyed')
|
|
304
|
+
return;
|
|
293
305
|
// Reset signals for a fresh request.
|
|
294
|
-
value.set(undefined);
|
|
295
306
|
isLoading.set(true);
|
|
296
|
-
|
|
307
|
+
errorResponse.set(undefined);
|
|
297
308
|
statusCode.set(undefined);
|
|
298
309
|
headers.set(undefined);
|
|
310
|
+
status.set('loading');
|
|
311
|
+
error.set(undefined);
|
|
299
312
|
// Unwrap the current request.
|
|
300
313
|
const req = currentRequest();
|
|
301
314
|
const url = req.url;
|
|
@@ -312,14 +325,20 @@ function createFetchSignal(request, method, transform, autoRefresh) {
|
|
|
312
325
|
}
|
|
313
326
|
uri += (uri.includes('?') ? '&' : '?') + searchParams.toString();
|
|
314
327
|
}
|
|
328
|
+
// Filter out undefined header values
|
|
329
|
+
const filteredHeaders = requestHeaders
|
|
330
|
+
? Object.fromEntries(Object.entries(requestHeaders).filter(([, value]) => value !== undefined))
|
|
331
|
+
: undefined;
|
|
315
332
|
try {
|
|
333
|
+
// send the request
|
|
316
334
|
const response = await fetch(uri, {
|
|
317
335
|
method,
|
|
318
|
-
headers:
|
|
336
|
+
headers: filteredHeaders,
|
|
319
337
|
// Only include a body if one is provided.
|
|
320
|
-
body: body
|
|
338
|
+
body: body,
|
|
321
339
|
signal: abortSignal,
|
|
322
340
|
});
|
|
341
|
+
// set the status code
|
|
323
342
|
statusCode.set(response.status);
|
|
324
343
|
// Extract response headers.
|
|
325
344
|
const headersObj = {};
|
|
@@ -330,57 +349,71 @@ function createFetchSignal(request, method, transform, autoRefresh) {
|
|
|
330
349
|
// if the response is ok, transform the body
|
|
331
350
|
if (response.ok) {
|
|
332
351
|
value.set(await transform(response));
|
|
333
|
-
|
|
352
|
+
status.set('resolved');
|
|
334
353
|
}
|
|
335
354
|
else {
|
|
336
355
|
// try to parse the error as json
|
|
337
|
-
// set the error to null if this fails
|
|
338
356
|
try {
|
|
339
|
-
|
|
357
|
+
errorResponse.set(await response.json());
|
|
358
|
+
value.set(undefined);
|
|
359
|
+
status.set('resolved');
|
|
340
360
|
}
|
|
341
361
|
catch {
|
|
342
|
-
error.
|
|
343
|
-
}
|
|
344
|
-
finally {
|
|
345
|
-
value.set(null);
|
|
362
|
+
throw new Error('Unable to parse error response.');
|
|
346
363
|
}
|
|
347
364
|
}
|
|
348
365
|
}
|
|
349
366
|
catch (err) {
|
|
350
|
-
if (err
|
|
351
|
-
|
|
367
|
+
if (err instanceof Error) {
|
|
368
|
+
// if the fetch request was aborted
|
|
369
|
+
// we check if we would like to continue loading (the next request)
|
|
370
|
+
// if so then we just leave this refresh in an undefined state
|
|
371
|
+
// else we error as usual
|
|
372
|
+
if (err.name === 'AbortError' && keepLoadingThroughAbort) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
error.set(err);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
// Fallback for non-Error values
|
|
379
|
+
error.set(new Error(String(err)));
|
|
352
380
|
}
|
|
353
|
-
|
|
354
|
-
|
|
381
|
+
value.set(undefined);
|
|
382
|
+
status.set('error');
|
|
355
383
|
}
|
|
356
|
-
persistentValue.set(value());
|
|
357
384
|
isLoading.set(false);
|
|
358
385
|
};
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
386
|
+
const destroy = () => {
|
|
387
|
+
// if the fetchSignal has been destroyed, do nothing
|
|
388
|
+
if (status() === 'destroyed')
|
|
389
|
+
return;
|
|
390
|
+
if (effectRef) {
|
|
391
|
+
effectRef.destroy();
|
|
392
|
+
}
|
|
393
|
+
status.set('destroyed');
|
|
394
|
+
value.set(undefined);
|
|
395
|
+
errorResponse.set(undefined);
|
|
396
|
+
isLoading.set(false);
|
|
397
|
+
statusCode.set(undefined);
|
|
398
|
+
headers.set(undefined);
|
|
399
|
+
error.set(undefined);
|
|
400
|
+
};
|
|
370
401
|
return {
|
|
371
402
|
value,
|
|
372
|
-
|
|
403
|
+
errorResponse,
|
|
373
404
|
isLoading,
|
|
374
|
-
error,
|
|
375
405
|
statusCode,
|
|
376
406
|
headers,
|
|
407
|
+
status,
|
|
408
|
+
error,
|
|
377
409
|
refresh,
|
|
410
|
+
destroy
|
|
378
411
|
};
|
|
379
412
|
}
|
|
380
413
|
//
|
|
381
|
-
// Helpers for attaching response transforms
|
|
414
|
+
// Helpers for attaching response transforms.
|
|
382
415
|
//
|
|
383
|
-
const createHelper = (method, transform) => (request,
|
|
416
|
+
const createHelper = (method, transform) => (request, options) => createFetchSignal(request, method, transform, options);
|
|
384
417
|
// Transforms
|
|
385
418
|
const jsonTransformer = (response) => response.json();
|
|
386
419
|
const textTransformer = (response) => response.text();
|
|
@@ -451,9 +484,9 @@ class UserManagementService {
|
|
|
451
484
|
created_before: createdBefore && createdBefore().toISOString(),
|
|
452
485
|
},
|
|
453
486
|
headers: {
|
|
454
|
-
Authorization: this.auth.bearerToken()
|
|
487
|
+
Authorization: this.auth.bearerToken()
|
|
455
488
|
}
|
|
456
|
-
}), true);
|
|
489
|
+
}), { autoRefresh: true });
|
|
457
490
|
}
|
|
458
491
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
459
492
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: UserManagementService, providedIn: 'root' });
|
|
@@ -492,11 +525,11 @@ class UserManagementComponent {
|
|
|
492
525
|
});
|
|
493
526
|
}
|
|
494
527
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: UserManagementComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
495
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.10", type: UserManagementComponent, isStandalone: true, selector: "app-user-management", viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<mat-form-field>\n <mat-label>Search for User</mat-label>\n <input matInput [(ngModel)]=\"search\">\n</mat-form-field>\n<mat-form-field>\n <mat-label>Account Type</mat-label>\n <mat-select multiple [(ngModel)]=\"accountTypes\">\n @for (accountType of accountTypeOptions; track accountType) {\n <mat-option [value]=\"accountType\">{{accountType}}</mat-option>\n }\n </mat-select>\n</mat-form-field>\n\n<table mat-table [dataSource]=\"getUsers.
|
|
528
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.10", type: UserManagementComponent, isStandalone: true, selector: "app-user-management", viewQueries: [{ propertyName: "paginator", first: true, predicate: MatPaginator, descendants: true }, { propertyName: "sort", first: true, predicate: MatSort, descendants: true }], ngImport: i0, template: "<mat-form-field>\n <mat-label>Search for User</mat-label>\n <input matInput [(ngModel)]=\"search\">\n</mat-form-field>\n<mat-form-field>\n <mat-label>Account Type</mat-label>\n <mat-select multiple [(ngModel)]=\"accountTypes\">\n @for (accountType of accountTypeOptions; track accountType) {\n <mat-option [value]=\"accountType\">{{accountType}}</mat-option>\n }\n </mat-select>\n</mat-form-field>\n\n<table mat-table [dataSource]=\"getUsers.value()?.data ?? []\" matSort matSortActive=\"netId\" matSortDisableClear matSortDirection=\"asc\">\n <!-- NetID Column -->\n <ng-container matColumnDef=\"netId\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>NetID</th>\n <td mat-cell *matCellDef=\"let user\">{{user.netId}}</td>\n </ng-container>\n\n <!-- First Name Column -->\n <ng-container matColumnDef=\"preferredFirstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>First Name</th>\n <td mat-cell *matCellDef=\"let user\">{{user.preferredFirstName}}</td>\n </ng-container>\n\n <!-- Last Name Column -->\n <ng-container matColumnDef=\"preferredLastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>Last Name</th>\n <td mat-cell *matCellDef=\"let user\">{{user.preferredLastName}}</td>\n </ng-container>\n\n <!-- Roles Column -->\n <ng-container matColumnDef=\"roles\">\n <th mat-header-cell *matHeaderCellDef>Roles</th>\n <td mat-cell *matCellDef=\"let user\">{{user.roles.join(', ')}}</td>\n </ng-container>\n\n <!-- Account Type Column -->\n <ng-container matColumnDef=\"accountType\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>Account Type</th>\n <td mat-cell *matCellDef=\"let user\">{{user.accountType}}</td>\n </ng-container>\n\n <!-- Created Column -->\n <ng-container matColumnDef=\"created\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n Created\n </th>\n <td mat-cell *matCellDef=\"let user\">{{user.created | date}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n\n<mat-paginator [length]=\"getUsers.value()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i1$1.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i1$1.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i1$1.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i1$1.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i1$1.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i1$1.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i1$1.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i1$1.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i1$1.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i1$1.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSortModule }, { kind: "directive", type: i2.MatSort, selector: "[matSort]", inputs: ["matSortActive", "matSortStart", "matSortDirection", "matSortDisableClear", "matSortDisabled"], outputs: ["matSortChange"], exportAs: ["matSort"] }, { kind: "component", type: i2.MatSortHeader, selector: "[mat-sort-header]", inputs: ["mat-sort-header", "arrowPosition", "start", "disabled", "sortActionDescription", "disableClear"], exportAs: ["matSortHeader"] }, { kind: "ngmodule", type: MatPaginatorModule }, { kind: "component", type: i3.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] });
|
|
496
529
|
}
|
|
497
530
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImport: i0, type: UserManagementComponent, decorators: [{
|
|
498
531
|
type: Component,
|
|
499
|
-
args: [{ selector: 'app-user-management', imports: [MatTableModule, MatSortModule, MatPaginatorModule, DatePipe, FormsModule, MatInputModule, MatSelectModule], template: "<mat-form-field>\n <mat-label>Search for User</mat-label>\n <input matInput [(ngModel)]=\"search\">\n</mat-form-field>\n<mat-form-field>\n <mat-label>Account Type</mat-label>\n <mat-select multiple [(ngModel)]=\"accountTypes\">\n @for (accountType of accountTypeOptions; track accountType) {\n <mat-option [value]=\"accountType\">{{accountType}}</mat-option>\n }\n </mat-select>\n</mat-form-field>\n\n<table mat-table [dataSource]=\"getUsers.
|
|
532
|
+
args: [{ selector: 'app-user-management', imports: [MatTableModule, MatSortModule, MatPaginatorModule, DatePipe, FormsModule, MatInputModule, MatSelectModule], template: "<mat-form-field>\n <mat-label>Search for User</mat-label>\n <input matInput [(ngModel)]=\"search\">\n</mat-form-field>\n<mat-form-field>\n <mat-label>Account Type</mat-label>\n <mat-select multiple [(ngModel)]=\"accountTypes\">\n @for (accountType of accountTypeOptions; track accountType) {\n <mat-option [value]=\"accountType\">{{accountType}}</mat-option>\n }\n </mat-select>\n</mat-form-field>\n\n<table mat-table [dataSource]=\"getUsers.value()?.data ?? []\" matSort matSortActive=\"netId\" matSortDisableClear matSortDirection=\"asc\">\n <!-- NetID Column -->\n <ng-container matColumnDef=\"netId\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>NetID</th>\n <td mat-cell *matCellDef=\"let user\">{{user.netId}}</td>\n </ng-container>\n\n <!-- First Name Column -->\n <ng-container matColumnDef=\"preferredFirstName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>First Name</th>\n <td mat-cell *matCellDef=\"let user\">{{user.preferredFirstName}}</td>\n </ng-container>\n\n <!-- Last Name Column -->\n <ng-container matColumnDef=\"preferredLastName\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>Last Name</th>\n <td mat-cell *matCellDef=\"let user\">{{user.preferredLastName}}</td>\n </ng-container>\n\n <!-- Roles Column -->\n <ng-container matColumnDef=\"roles\">\n <th mat-header-cell *matHeaderCellDef>Roles</th>\n <td mat-cell *matCellDef=\"let user\">{{user.roles.join(', ')}}</td>\n </ng-container>\n\n <!-- Account Type Column -->\n <ng-container matColumnDef=\"accountType\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>Account Type</th>\n <td mat-cell *matCellDef=\"let user\">{{user.accountType}}</td>\n </ng-container>\n\n <!-- Created Column -->\n <ng-container matColumnDef=\"created\">\n <th mat-header-cell *matHeaderCellDef mat-sort-header>\n Created\n </th>\n <td mat-cell *matCellDef=\"let user\">{{user.created | date}}</td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n</table>\n\n<mat-paginator [length]=\"getUsers.value()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n" }]
|
|
500
533
|
}], propDecorators: { paginator: [{
|
|
501
534
|
type: ViewChild,
|
|
502
535
|
args: [MatPaginator]
|
|
@@ -594,6 +627,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImpo
|
|
|
594
627
|
args: [{ selector: 'fhss-not-found', imports: [MatButtonModule, ByuHeaderComponent, ByuFooterComponent], template: "<byu-header />\n\n<div class=\"container\">\n <div class=\"not-found\">\n <img src=\"/confused-duck.png\" alt=\"\" />\n <h1>404 - Page not found</h1>\n <p>The page you are looking for doesn't exist or it may have moved.</p>\n <a mat-flat-button href=\"/\">Go back to home</a>\n </div>\n</div>\n\n<byu-footer />\n", styles: [":host{height:100vh;display:flex;flex-direction:column}.container{flex:1;display:flex;align-items:center;justify-content:center}.container .not-found{text-align:center;padding-bottom:3em}\n"] }]
|
|
595
628
|
}] });
|
|
596
629
|
|
|
630
|
+
function trpcResource(procedure, input, options) {
|
|
631
|
+
const currentInput = computed(input);
|
|
632
|
+
const value = signal(options?.defaultValue, { equal: options?.equal });
|
|
633
|
+
const error = signal(undefined);
|
|
634
|
+
const isLoading = signal(false);
|
|
635
|
+
const resourceError = signal(undefined);
|
|
636
|
+
const injector = inject(Injector);
|
|
637
|
+
let effectRef = undefined;
|
|
638
|
+
if (options?.autoRefresh) {
|
|
639
|
+
effectRef = effect((onCleanup) => {
|
|
640
|
+
// pass abort signal to refresh on cleanup of effect
|
|
641
|
+
const controller = new AbortController();
|
|
642
|
+
onCleanup(() => controller.abort());
|
|
643
|
+
// call refresh with this abort controller
|
|
644
|
+
// refresh reads currentInput which triggers the effect
|
|
645
|
+
refresh(controller.signal, true);
|
|
646
|
+
}, { injector: options?.injector || injector });
|
|
647
|
+
}
|
|
648
|
+
const refresh = async (abortSignal, keepLoadingThroughAbort = true) => {
|
|
649
|
+
// Reset signals for a fresh request.
|
|
650
|
+
isLoading.set(true);
|
|
651
|
+
error.set(undefined);
|
|
652
|
+
resourceError.set(undefined);
|
|
653
|
+
try {
|
|
654
|
+
value.set(await procedure(currentInput(), {
|
|
655
|
+
signal: abortSignal
|
|
656
|
+
}));
|
|
657
|
+
error.set(undefined);
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
if (isTRPCClientError(err)) {
|
|
661
|
+
// if the trpc request was aborted
|
|
662
|
+
// we check if we would like to continue loading (the next request)
|
|
663
|
+
// if so then we just leave this refresh in an undefined state
|
|
664
|
+
// else we error as usual
|
|
665
|
+
if (err.cause?.name === 'AbortError' && keepLoadingThroughAbort) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
error.set(err);
|
|
669
|
+
}
|
|
670
|
+
else if (err instanceof Error) {
|
|
671
|
+
resourceError.set(err);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Fallback for non-Error values
|
|
675
|
+
resourceError.set(new Error(String(err)));
|
|
676
|
+
}
|
|
677
|
+
value.set(options?.defaultValue);
|
|
678
|
+
}
|
|
679
|
+
isLoading.set(false);
|
|
680
|
+
};
|
|
681
|
+
return {
|
|
682
|
+
value,
|
|
683
|
+
error,
|
|
684
|
+
isLoading,
|
|
685
|
+
resourceError,
|
|
686
|
+
refresh
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function debugTrpcResource(_trpcResource) {
|
|
690
|
+
return {
|
|
691
|
+
value: _trpcResource.value(),
|
|
692
|
+
error: _trpcResource.error(),
|
|
693
|
+
isLoading: _trpcResource.isLoading(),
|
|
694
|
+
resourceError: _trpcResource.resourceError(),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
597
698
|
/**
|
|
598
699
|
* Components
|
|
599
700
|
*/
|
|
@@ -602,5 +703,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.10", ngImpo
|
|
|
602
703
|
* Generated bundle index. Do not edit.
|
|
603
704
|
*/
|
|
604
705
|
|
|
605
|
-
export { AuthCallbackPage, AuthErrorPage, AuthService, ByuFooterComponent, ByuHeaderComponent, FHSS_CONFIG, ForbiddenPage, NotFoundPage, UserManagementComponent, authGuard, debounced, enumToRecord, fetchSignal, provideFhss,
|
|
706
|
+
export { AuthCallbackPage, AuthErrorPage, AuthService, ByuFooterComponent, ByuHeaderComponent, FHSS_CONFIG, ForbiddenPage, NotFoundPage, UserManagementComponent, authGuard, debounced, debugTrpcResource, enumToRecord, fetchSignal, provideFhss, roleGuardFactory };
|
|
606
707
|
//# sourceMappingURL=fhss-web-team-frontend-utils.mjs.map
|