@fhss-web-team/frontend-utils 0.0.7 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,63 +1,210 @@
1
1
  # FrontendUtils
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.0.
3
+ `@fhss-web-team/frontend-utils` is a shared Angular library for reusable components, services, and signals used across FHSS websites.
4
4
 
5
- ## Code scaffolding
5
+ This package is designed for use in Angular applications and includes integration with Keycloak for authentication.
6
6
 
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
7
+ ## Installation
8
+
9
+ Install the package and its required peer dependencies:
8
10
 
9
11
  ```bash
10
- ng generate component component-name
12
+ npm install @fhss-web-team/frontend-utils
11
13
  ```
12
14
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
-
15
- ```bash
16
- ng generate --help
15
+ # Documentation
16
+ - [Components](#components)
17
+ - [Services](#services)
18
+ - [Signals](#signals)
19
+
20
+ ## Components
21
+
22
+ This directory contains reusable Angular components designed to streamline development and maintain consistency across BYU websites.
23
+
24
+ ### BYU Header
25
+ This is the header displayed on all BYU websites. It displays the BYU logo, site title, breadcrumbs, and navigation menu.
26
+
27
+ #### Features:
28
+ - **Logo**: Displays the BYU logo.
29
+ - **Title and Subtitle**: Configurable links for the site title and subtitle.
30
+ - **Breadcrumbs**: Displays a breadcrumb trail for navigation.
31
+ - **Navigation Menu**: Supports both simple links and dropdown menus.
32
+ - **Authentication**: Integrates with the [auth service](#auth) to display user information and provide sign-in/sign-out functionality.
33
+
34
+ #### Configuration:
35
+ The header is configured using a `HeaderConfig` object. Below is the structure:
36
+
37
+ ```typescript
38
+ type HeaderLink = {
39
+ text: string;
40
+ path: string;
41
+ };
42
+
43
+ type HeaderMenu = HeaderLink | {
44
+ text: string;
45
+ items: HeaderLink[];
46
+ };
47
+
48
+ export type HeaderConfig = {
49
+ title: HeaderLink;
50
+ subtitle?: HeaderLink;
51
+ breadcrumbs?: HeaderLink[];
52
+ menu?: HeaderMenu[];
53
+ };
17
54
  ```
18
55
 
19
- ## Building
56
+ #### Usage:
57
+ Pass the `HeaderConfig` object to the `byu-header` component:
20
58
 
21
- To build the library, run:
59
+ ```html
60
+ <byu-header [config]="headerConfig"></byu-header>
61
+ ```
22
62
 
23
- ```bash
24
- ng build frontend-utils
63
+ Example `HeaderConfig`:
64
+
65
+ ```typescript
66
+ const headerConfig = {
67
+ title: { text: 'Home', path: '/' },
68
+ subtitle: { text: 'About Us', path: '/about' },
69
+ breadcrumbs: [
70
+ { text: 'Home', path: '/' },
71
+ { text: 'Section', path: '/section' }
72
+ ],
73
+ menu: [
74
+ { text: 'Services', path: '/services' },
75
+ {
76
+ text: 'More',
77
+ items: [
78
+ { text: 'Contact', path: '/contact' },
79
+ { text: 'Help', path: '/help' }
80
+ ]
81
+ }
82
+ ]
83
+ };
25
84
  ```
26
85
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
86
+ ---
87
+
88
+ ### BYU Footer
89
+ This is the footer displayed on all BYU websites. It provides essential information and links to comply with legal and institutional requirements.
28
90
 
29
- ### Publishing the Library
91
+ #### Features:
92
+ - **University Name**: Displays "Brigham Young University" with a link to the main BYU website.
93
+ - **Address**: Displays the university's address.
94
+ - **Copyright Notice**: Automatically updates the year to the current year.
95
+ - **Privacy Links**: Includes links to the Privacy Notice and Cookie Preferences pages.
30
96
 
31
- Once the project is built, you can publish your library by following these steps:
97
+ #### Usage:
98
+ Add the `byu-footer` component to your template:
32
99
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/frontend-utils
36
- ```
100
+ ```html
101
+ <byu-footer />
102
+ ```
37
103
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
104
+ The footer does not require any configuration and is ready to use out of the box.
42
105
 
43
- ## Running unit tests
106
+ ---
44
107
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
108
+ ### User Management Table
109
+ This component provides a table for managing user data, including search, filtering, sorting, and pagination functionalities. It will use the site's backend's `/api/user-management/` route.
46
110
 
47
- ```bash
48
- ng test
111
+ #### Features:
112
+ - **Search**: Allows searching for users by their NetID or other attributes.
113
+ - **Filtering**: Supports filtering by account types (e.g., NonBYU, Student, Employee).
114
+ - **Sorting**: Columns can be sorted by attributes such as NetID, First Name, Last Name, Account Type, and Created Date.
115
+ - **Pagination**: Includes a paginator to navigate through large datasets.
116
+ - **Dynamic Data**: Fetches user data dynamically from the server.
117
+
118
+ #### Configuration:
119
+ The table uses the following signals for configuration:
120
+ - `search`: A string for filtering users by search terms.
121
+ - `accountTypes`: An array of account types to filter users.
122
+ - `sortBy`: The column to sort by (e.g., `netId`, `preferredFirstName`).
123
+ - `sortDirection`: The sorting direction (`asc` or `desc`).
124
+ - `pageCount`: The number of rows per page.
125
+ - `pageOffset`: The offset for pagination.
126
+ - `createdAfter` and `createdBefore`: Filtering by creationg date
127
+
128
+ #### Usage:
129
+ Add the `app-user-management` component to your template:
130
+
131
+ ```html
132
+ <app-user-management />
49
133
  ```
50
134
 
51
- ## Running end-to-end tests
135
+ The component automatically handles data fetching and UI updates based on user interactions.
52
136
 
53
- For end-to-end (e2e) testing, run:
54
137
 
55
- ```bash
56
- ng e2e
138
+
139
+ ## Services
140
+
141
+ The services in this library provide reusable logic and utilities to simplify common tasks such as authentication, data fetching, and state management. They are designed to integrate seamlessly with Angular applications and follow best practices for dependency injection and reactive programming.
142
+
143
+ ### Auth
144
+ Wraps the `keycloak-angular` service to provide a simplified interface for authentication and authorization.
145
+
146
+ #### Features:
147
+ - **Login and Logout**: Methods to handle user login and logout.
148
+ - **Authentication Signals**: Signals to track authentication status, tokens, and user information.
149
+ - **Token Management**: Provides access to tokens (e.g., `token`, `idToken`, `refreshToken`) and their parsed forms.
150
+ - **Role and Access Management**: Signals for realm and resource roles.
151
+ - **Time Skew and Configuration**: Signals for Keycloak configuration properties like `timeSkew`, `responseMode`, and `flow`.
152
+
153
+
154
+
155
+
156
+ ## Signals
157
+
158
+ ### fetchSignal
159
+ The `fetchSignal` helper provides a reactive way to perform HTTP requests in Angular applications. It supports various HTTP methods and automatically tracks dependencies for reactivity.
160
+
161
+ #### Features:
162
+ - **Reactive Requests**: Automatically updates when input signals change.
163
+ - **Support for Multiple Methods**: Includes `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`.
164
+ - **Response Parsing**: Supports JSON, text, Blob, and ArrayBuffer responses.
165
+ - **Error Handling**: Tracks errors and status codes reactively.
166
+ - **Auto-Refresh**: Automatically refreshes requests when dependencies change.
167
+
168
+ #### Usage:
169
+ ```typescript
170
+ import { fetchSignal } from '@fhss-web-team/frontend-utils';
171
+
172
+ // Example: Reactive GET request
173
+ const request = () => ({
174
+ url: '/api/data',
175
+ params: { filter: 'active' },
176
+ });
177
+ const dataSignal = fetchSignal.json(request, true);
178
+
179
+ // Access reactive properties
180
+ dataSignal.value(); // Current response value
181
+ dataSignal.isLoading(); // Loading state
182
+ dataSignal.error(); // Error details
183
+ dataSignal.refresh(); // Manually refresh the request
57
184
  ```
58
185
 
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
186
+ ---
187
+
188
+ ### debounced
189
+ The `debounced` helper creates a debounced version of a signal, delaying updates until after a specified wait time.
190
+
191
+ #### Features:
192
+ - **Debounced Updates**: Reduces the frequency of updates to a signal.
193
+ - **Customizable Delay**: Specify the debounce delay in milliseconds.
194
+
195
+ #### Usage:
196
+ ```typescript
197
+ import { signal } from '@angular/core';
198
+ import { debounced } from '@fhss-web-team/frontend-utils';
60
199
 
61
- ## Additional Resources
200
+ // Example: Debounced search input
201
+ const searchInput = signal('');
202
+ const debouncedSearch = debounced(searchInput, 300);
203
+
204
+ // Update the input signal
205
+ searchInput.set('new search term');
206
+
207
+ // The debounced signal updates after 300ms
208
+ debouncedSearch(); // 'new search term' (after delay)
209
+ ```
62
210
 
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
@@ -29,13 +29,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImpor
29
29
  }] });
30
30
 
31
31
  /**
32
- * FIXME: Finish the wrapper for all properties and methods
33
- *
34
- * See https://www.keycloak.org/securing-apps/javascript-adapter
32
+ * AuthService provides a wrapper around the Keycloak JavaScript adapter, exposing its properties and methods
33
+ * as Angular signals for reactive programming. It simplifies authentication, token management, and role-based access control.
35
34
  */
36
35
  class AuthService {
37
36
  keycloak = inject(Keycloak);
38
37
  keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
38
+ /**
39
+ * Initiates the login process. Optionally stores a route to redirect to after login.
40
+ * @param nextRoute - The route to navigate to after successful login.
41
+ */
39
42
  login = (nextRoute) => {
40
43
  if (nextRoute) {
41
44
  sessionStorage.setItem('nextRoute', nextRoute);
@@ -44,134 +47,109 @@ class AuthService {
44
47
  redirectUri: window.location.origin + '/auth-callback',
45
48
  });
46
49
  };
50
+ /**
51
+ * Logs the user out of the application.
52
+ * @param options - Optional logout options.
53
+ */
47
54
  logout = (options) => this.keycloak.logout(options);
48
55
  /**
49
- * A signal for the keycloak-js authenticated property
50
- *
51
- * Is `true` if the user is authenticated, `false` otherwise.
56
+ * Signal indicating whether the user is authenticated.
57
+ * Returns `true` if authenticated, `false` otherwise.
52
58
  */
53
59
  authenticated = computed(() => {
54
60
  this.keycloakSignal();
55
61
  return this.keycloak.authenticated;
56
62
  });
57
63
  /**
58
- * A signal for the keycloak-js token property
59
- *
60
- * The base64 encoded token that can be sent in the `Authorization` header in requests to services.
64
+ * Signal for the base64-encoded token used in the `Authorization` header.
61
65
  */
62
66
  token = computed(() => {
63
67
  this.keycloakSignal();
64
68
  return this.keycloak.token;
65
69
  });
66
70
  /**
67
- * A signal that will return the Bearer Token for use in the Authorization header
71
+ * Signal for the Bearer token, prefixed with "Bearer ".
68
72
  */
69
73
  bearerToken = computed(() => (this.token() ? 'Bearer ' + this.token() : undefined));
70
74
  /**
71
- * A signal for the keycloak-js tokenParsed property
72
- *
73
- * The parsed token as a JavaScript object.
75
+ * Signal for the parsed token as a JavaScript object.
74
76
  */
75
77
  tokenParsed = computed(() => {
76
78
  this.keycloakSignal();
77
79
  return this.keycloak.tokenParsed;
78
80
  });
79
81
  /**
80
- * A signal for the keycloak-js subject property
81
- *
82
- * The user id.
82
+ * Signal for the user ID (Keycloak subject).
83
83
  */
84
84
  userId = computed(() => {
85
85
  this.keycloakSignal();
86
86
  return this.keycloak.subject;
87
87
  });
88
88
  /**
89
- * A signal for the keycloak-js idToken property
90
- *
91
- * The base64 encoded ID token.
89
+ * Signal for the base64-encoded ID token.
92
90
  */
93
91
  idToken = computed(() => {
94
92
  this.keycloakSignal();
95
93
  return this.keycloak.idToken;
96
94
  });
97
95
  /**
98
- * A signal for the keycloak-js idTokenParsed property
99
- *
100
- * The parsed id token as a JavaScript object.
96
+ * Signal for the parsed ID token as a JavaScript object.
101
97
  */
102
98
  idTokenParsed = computed(() => {
103
99
  this.keycloakSignal();
104
100
  return this.keycloak.idTokenParsed;
105
101
  });
106
102
  /**
107
- * A signal for the keycloak-js realmAccess property
108
- *
109
- * The realm roles associated with the token.
103
+ * Signal for the realm roles associated with the token.
110
104
  */
111
105
  realmAccess = computed(() => {
112
106
  this.keycloakSignal();
113
107
  return this.keycloak.realmAccess;
114
108
  });
115
109
  /**
116
- * A signal for the keycloak-js resourceAccess property
117
- *
118
- * The resource roles associated with the token.
110
+ * Signal for the resource roles associated with the token.
119
111
  */
120
112
  resourceAccess = computed(() => {
121
113
  this.keycloakSignal();
122
114
  return this.keycloak.resourceAccess;
123
115
  });
124
116
  /**
125
- * A signal for the keycloak-js refreshToken property
126
- *
127
- * The base64 encoded refresh token that can be used to retrieve a new token.
117
+ * Signal for the base64-encoded refresh token.
128
118
  */
129
119
  refreshToken = computed(() => {
130
120
  this.keycloakSignal();
131
121
  return this.keycloak.refreshToken;
132
122
  });
133
123
  /**
134
- * A signal for the keycloak-js refreshTokenParsed property
135
- *
136
- * The parsed refresh token as a JavaScript object.
124
+ * Signal for the parsed refresh token as a JavaScript object.
137
125
  */
138
126
  refreshTokenParsed = computed(() => {
139
127
  this.keycloakSignal();
140
128
  return this.keycloak.refreshTokenParsed;
141
129
  });
142
130
  /**
143
- * A signal for the keycloak-js timeSkew property
144
- *
145
- * The estimated time difference between the browser time and the Keycloak server in seconds.
146
- * This value is just an estimation, but is accurate enough when determining if a token is expired or not.
131
+ * Signal for the estimated time difference between the browser and Keycloak server in seconds.
147
132
  */
148
133
  timeSkew = computed(() => {
149
134
  this.keycloakSignal();
150
135
  return this.keycloak.timeSkew;
151
136
  });
152
137
  /**
153
- * A signal for the keycloak-js responseMode property
154
- *
155
- * Response mode passed in init (default value is fragment).
138
+ * Signal for the response mode passed during initialization.
156
139
  */
157
140
  responseMode = computed(() => {
158
141
  this.keycloakSignal();
159
142
  return this.keycloak.responseMode;
160
143
  });
161
144
  /**
162
- * A signal for the keycloak-js flow property
163
- *
164
- * Flow passed in init.
145
+ * Signal for the flow type used during initialization.
165
146
  */
166
147
  flow = computed(() => {
167
148
  this.keycloakSignal();
168
149
  return this.keycloak.flow;
169
150
  });
170
151
  /**
171
- * A signal for the keycloak-js responseType property
172
- *
173
- * Response type sent to Keycloak with login requests. This is determined based on the flow value used during initialization,
174
- * but can be overridden by setting this value.
152
+ * Signal for the response type sent to Keycloak during login requests.
175
153
  */
176
154
  responseType = computed(() => {
177
155
  this.keycloakSignal();
@@ -211,6 +189,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImpor
211
189
  args: [{ selector: 'byu-header', imports: [RouterModule], template: "<header>\n <div class=\"top\" >\n <img class=\"logo\" src=\"/BYU_monogram_white@2x.png\" alt=\"BYU\">\n <div class=\"titles\"> \n <div class=\"breadcrumbs\">\n @for (breadcrumb of config()?.breadcrumbs; track breadcrumb.text){\n <a [href]=\"breadcrumb.path\" >{{ breadcrumb.text }}</a>\n }\n </div>\n <a [routerLink]=\"config()?.title?.path\" class=\"title\">{{ config()?.title?.text }}</a>\n <a [routerLink]=\"config()?.subtitle?.path\" class=\"subtitle\">{{ config()?.subtitle?.text }}</a>\n </div>\n <div class=\"signin\">\n <p>{{ this.auth.tokenParsed()?.['given_name'] ?? '' }}</p>\n <svg class=\"signin-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n <path fill=\"currentcolor\" d=\"M50 95c-26 0-34-18-34-18 3-12 8-18 17-18 5 5 10 7 17 7s12-2 17-7c9 0 14 6 17 18 0 0-7 18-34 18z\"></path>\n <circle cx=\"50\" cy=\"50\" r=\"45\" fill=\"none\" stroke=\"currentcolor\" stroke-width=\"10\"></circle>\n <circle fill=\"currentcolor\" cx=\"50\" cy=\"40\" r=\"20\"></circle>\n </svg>\n @if (auth.authenticated()) {\n <a class=\"signin-link\" (click)=\"auth.logout()\">Sign Out</a>\n } @else {\n <a class=\"signin-link\" (click)=\"auth.login()\">Sign In</a>\n }\n </div>\n </div>\n <div class=\"bottom\">\n <nav>\n @for (menuItem of config()?.menu; track menuItem.text){>\n @if (isHeaderLink(menuItem)) {\n <li class = \"nav-item\">\n <a class=\"nav-item-content\" [routerLink]=\"menuItem.path\">{{ menuItem.text }}</a>\n </li>\n } @else {\n <li class=\"nav-item dropdown\" (click)=\"toggleDropdown(menuItem.text)\">\n <div class=\"nav-item-content\" >{{ menuItem.text }}</div>\n @if (openDropdownText === menuItem.text) {\n <ul class = \"dropdown-item-menu\"> \n @for(dropItem of menuItem.items; track dropItem.text){\n <li class = \"dropdown-item\">\n <a class=\"dropdown-item-content\" [routerLink]=\"dropItem.path\">{{ dropItem.text }}</a>\n </li>\n }\n </ul>\n }\n </li>\n }\n }\n </nav>\n </div>\n</header>\n", styles: ["header{font-family:HCo Ringside Narrow SSm,Open Sans,Helvetica,Arial,sans-serif;color:#fff}header .top{background-color:#002e5d;display:flex;align-items:center;padding:13px 16px;gap:16px}header .top .logo{width:100px}header .top .titles{display:flex;flex-direction:column;gap:8px;padding-left:30px;border-left:1px solid rgba(255,255,255,.25)}header .top .titles .breadcrumbs{display:flex;flex-direction:row}header .top .titles .breadcrumbs a{color:#a6abb1;text-decoration:none;font-size:16px}header .top .titles .breadcrumbs a:not(:first-child){padding-left:10px}header .top .titles .breadcrumbs a:not(:last-child){padding-right:10px;border-right:1px solid rgba(255,255,255,.25)}header .top .titles .breadcrumbs a:hover{color:#fff}header .top .titles .title{color:inherit;font-size:24px;font-weight:500;text-decoration:none}header .top .titles .subtitle{color:inherit;font-size:16px;font-weight:500;text-decoration:none}header .top .signin{display:flex;align-items:center;gap:10px;font-size:16px;margin-left:auto}header .top .signin .signin-icon{display:flex;margin-left:auto;margin-right:0;align-items:center;height:20px;width:20px}header .top .signin .signin-link{color:#fff;text-decoration:none;cursor:pointer}header .bottom{box-shadow:0 3px 10px #ccc5c580}header .bottom nav{display:flex;flex-direction:row;padding-left:124px}header .bottom nav .nav-item{display:block;list-style:none;transition:all .25s;text-decoration:none}header .bottom nav .nav-item:hover{box-shadow:inset 0 -5px #002e5d;background-color:#f1f1f1}header .bottom nav .nav-item .nav-item-content{margin:5px;display:inline-block;padding:11px 22px;text-decoration:none;color:#002e5d}header .bottom nav .nav-item.dropdown{position:relative}header .bottom nav .nav-item.dropdown .dropdown-item-menu{position:absolute;background:#fff;z-index:1000;top:100%;width:min-content;margin:-5px;padding:0;box-shadow:0 3px 3px #ccc5c5bf}header .bottom nav .nav-item.dropdown .dropdown-item-menu .dropdown-item:hover{background-color:#f1f1f1}header .bottom nav .nav-item.dropdown .dropdown-item-menu .dropdown-item .dropdown-item-content{margin:10px;display:inline-block;text-decoration:none;text-wrap:nowrap;color:#002e5d;padding:11px 22px}\n"] }]
212
190
  }] });
213
191
 
192
+ const debounced = (inputSignal, wait = 400) => {
193
+ const debouncedSignal = signal(inputSignal());
194
+ const setSignal = debounce((value) => debouncedSignal.set(value), wait);
195
+ effect(() => {
196
+ setSignal(inputSignal());
197
+ });
198
+ return debouncedSignal;
199
+ };
200
+ const debounce = (callback, wait) => {
201
+ let timeoutId;
202
+ return (...args) => {
203
+ window.clearTimeout(timeoutId);
204
+ timeoutId = window.setTimeout(() => {
205
+ callback(...args);
206
+ }, wait);
207
+ };
208
+ };
209
+
214
210
  /**
215
211
  * Reads the underlying value from a MaybeSignal.
216
212
  *
@@ -381,67 +377,45 @@ fetchSignal.delete.text = createHelper('DELETE', textTransformer);
381
377
  fetchSignal.delete.blob = createHelper('DELETE', blobTransformer);
382
378
  fetchSignal.delete.arrayBuffer = createHelper('DELETE', arrayBufferTransformer);
383
379
 
384
- class UserManagementService {
385
- auth = inject(AuthService);
386
- getUsers(search, accountTypes, sortBy, sortDirection, pageCount, pageOffset, createdAfter, createdBefore) {
387
- return fetchSignal(() => ({
388
- url: '/api/user-management',
389
- params: {
390
- search: search(),
391
- account_types: accountTypes().join(','),
392
- sort_by: sortBy(),
393
- sort_direction: sortDirection(),
394
- page_count: pageCount(),
395
- page_offset: pageOffset(),
396
- created_after: createdAfter && createdAfter().toISOString(),
397
- created_before: createdBefore && createdBefore().toISOString(),
398
- },
399
- headers: {
400
- Authorization: this.auth.bearerToken() ?? ''
401
- }
402
- }), true);
403
- }
404
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
405
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: UserManagementService, providedIn: 'root' });
406
- }
407
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: UserManagementService, decorators: [{
408
- type: Injectable,
409
- args: [{
410
- providedIn: 'root'
411
- }]
412
- }] });
413
-
414
- const debounced = (inputSignal, wait = 400) => {
415
- const debouncedSignal = signal(inputSignal());
416
- const setSignal = debounce((value) => debouncedSignal.set(value), wait);
417
- effect(() => {
418
- setSignal(inputSignal());
419
- });
420
- return debouncedSignal;
421
- };
422
- const debounce = (callback, wait) => {
423
- let timeoutId;
424
- return (...args) => {
425
- window.clearTimeout(timeoutId);
426
- timeoutId = window.setTimeout(() => {
427
- callback(...args);
428
- }, wait);
429
- };
430
- };
431
-
432
380
  class UserManagementComponent {
433
- api = inject(UserManagementService);
434
- displayedColumns = ['netId', 'preferredFirstName', 'preferredLastName', 'roles', 'accountType', 'created'];
381
+ auth = inject(AuthService);
382
+ displayedColumns = [
383
+ 'netId',
384
+ 'preferredFirstName',
385
+ 'preferredLastName',
386
+ 'roles',
387
+ 'accountType',
388
+ 'created',
389
+ ];
435
390
  accountTypeOptions = ['NonBYU', 'Student', 'Employee'];
436
391
  paginator;
437
392
  sort;
438
393
  search = signal('');
439
- accountTypes = signal(['NonBYU', 'Student', 'Employee']);
394
+ accountTypes = signal([
395
+ 'NonBYU',
396
+ 'Student',
397
+ 'Employee',
398
+ ]);
440
399
  sortBy = signal('netId');
441
400
  sortDirection = signal('asc');
442
401
  pageCount = signal(5);
443
402
  pageOffset = signal(0);
444
- getUsers = this.api.getUsers(debounced(this.search), this.accountTypes, this.sortBy, this.sortDirection, this.pageCount, this.pageOffset);
403
+ getUsers = fetchSignal(() => ({
404
+ url: '/api/user-management',
405
+ params: {
406
+ search: debounced(this.search)(),
407
+ account_types: this.accountTypes().join(','),
408
+ sort_by: this.sortBy(),
409
+ sort_direction: this.sortDirection(),
410
+ page_count: this.pageCount(),
411
+ page_offset: this.pageOffset(),
412
+ // created_after: this.createdAfter().toISOString(),
413
+ // created_before: this.createdBefore().toISOString(),
414
+ },
415
+ headers: {
416
+ Authorization: this.auth.bearerToken() ?? '',
417
+ },
418
+ }), true);
445
419
  ngAfterViewInit() {
446
420
  // write the observables from the sort and the paging to the signals
447
421
  this.sort.sortChange.subscribe(({ active, direction }) => {
@@ -460,7 +434,15 @@ class UserManagementComponent {
460
434
  }
461
435
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: UserManagementComponent, decorators: [{
462
436
  type: Component,
463
- 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.persistentValue()?.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.persistentValue()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n" }]
437
+ args: [{ selector: 'app-user-management', imports: [
438
+ MatTableModule,
439
+ MatSortModule,
440
+ MatPaginatorModule,
441
+ DatePipe,
442
+ FormsModule,
443
+ MatInputModule,
444
+ MatSelectModule,
445
+ ], 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.persistentValue()?.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.persistentValue()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n" }]
464
446
  }], propDecorators: { paginator: [{
465
447
  type: ViewChild,
466
448
  args: [MatPaginator]
@@ -1 +1 @@
1
- {"version":3,"file":"fhss-web-team-frontend-utils.mjs","sources":["../../../projects/frontend-utils/src/lib/components/byu-footer/byu-footer.component.ts","../../../projects/frontend-utils/src/lib/components/byu-footer/byu-footer.component.html","../../../projects/frontend-utils/src/lib/services/auth/auth.service.ts","../../../projects/frontend-utils/src/lib/components/byu-header/byu-header.component.ts","../../../projects/frontend-utils/src/lib/components/byu-header/byu-header.component.html","../../../projects/frontend-utils/src/lib/signals/fetch-signal/fetch-signal.ts","../../../projects/frontend-utils/src/lib/services/user-management/user-management.service.ts","../../../projects/frontend-utils/src/lib/signals/debounced/debounced.ts","../../../projects/frontend-utils/src/lib/components/user-management/user-management.component.ts","../../../projects/frontend-utils/src/lib/components/user-management/user-management.component.html","../../../projects/frontend-utils/src/public-api.ts","../../../projects/frontend-utils/src/fhss-web-team-frontend-utils.ts"],"sourcesContent":["import { Component } from '@angular/core';\n\n@Component({\n selector: 'byu-footer',\n imports: [],\n templateUrl: './byu-footer.component.html',\n styleUrl: './byu-footer.component.scss'\n})\nexport class ByuFooterComponent {\n currentYear: number = new Date().getFullYear(); // Automatically updates the year\n}\n","<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | © {{ currentYear }} All rights reserved.</p>\n <p>\n <a href=\"https://privacy.byu.edu/privacy-notice\">Privacy Notice</a> |\n <a href=\"https://privacy.byu.edu/cookie-prefs\">Cookie Preferences</a>\n </p>\n</footer>\n \n\n","import { Injectable, inject, computed } from '@angular/core';\nimport { KEYCLOAK_EVENT_SIGNAL } from 'keycloak-angular';\nimport Keycloak, {\n KeycloakFlow,\n KeycloakLoginOptions,\n KeycloakLogoutOptions,\n KeycloakResourceAccess,\n KeycloakResponseMode,\n KeycloakResponseType,\n KeycloakRoles,\n KeycloakTokenParsed,\n} from 'keycloak-js';\n\n@Injectable({\n providedIn: 'root',\n})\n/**\n * FIXME: Finish the wrapper for all properties and methods\n *\n * See https://www.keycloak.org/securing-apps/javascript-adapter\n */\nexport class AuthService {\n private keycloak = inject(Keycloak);\n private keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);\n\n login = (nextRoute?: string) => {\n if (nextRoute) {\n sessionStorage.setItem('nextRoute', nextRoute);\n }\n this.keycloak.login({\n redirectUri: window.location.origin + '/auth-callback',\n });\n };\n logout = (options?: KeycloakLogoutOptions) => this.keycloak.logout(options);\n\n /**\n * A signal for the keycloak-js authenticated property\n *\n * Is `true` if the user is authenticated, `false` otherwise.\n */\n authenticated = computed<boolean | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.authenticated;\n });\n\n /**\n * A signal for the keycloak-js token property\n *\n * The base64 encoded token that can be sent in the `Authorization` header in requests to services.\n */\n token = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.token;\n });\n\n /**\n * A signal that will return the Bearer Token for use in the Authorization header\n */\n bearerToken = computed<string | undefined>(() => (this.token() ? 'Bearer ' + this.token() : undefined));\n\n /**\n * A signal for the keycloak-js tokenParsed property\n *\n * The parsed token as a JavaScript object.\n */\n tokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.tokenParsed;\n });\n\n /**\n * A signal for the keycloak-js subject property\n *\n * The user id.\n */\n userId = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.subject;\n });\n\n /**\n * A signal for the keycloak-js idToken property\n *\n * The base64 encoded ID token.\n */\n idToken = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.idToken;\n });\n\n /**\n * A signal for the keycloak-js idTokenParsed property\n *\n * The parsed id token as a JavaScript object.\n */\n idTokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.idTokenParsed;\n });\n\n /**\n * A signal for the keycloak-js realmAccess property\n *\n * The realm roles associated with the token.\n */\n realmAccess = computed<KeycloakRoles | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.realmAccess;\n });\n\n /**\n * A signal for the keycloak-js resourceAccess property\n *\n * The resource roles associated with the token.\n */\n resourceAccess = computed<KeycloakResourceAccess | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.resourceAccess;\n });\n\n /**\n * A signal for the keycloak-js refreshToken property\n *\n * The base64 encoded refresh token that can be used to retrieve a new token.\n */\n refreshToken = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.refreshToken;\n });\n\n /**\n * A signal for the keycloak-js refreshTokenParsed property\n *\n * The parsed refresh token as a JavaScript object.\n */\n refreshTokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.refreshTokenParsed;\n });\n\n /**\n * A signal for the keycloak-js timeSkew property\n *\n * The estimated time difference between the browser time and the Keycloak server in seconds.\n * This value is just an estimation, but is accurate enough when determining if a token is expired or not.\n */\n timeSkew = computed<number | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.timeSkew;\n });\n\n /**\n * A signal for the keycloak-js responseMode property\n *\n * Response mode passed in init (default value is fragment).\n */\n responseMode = computed<KeycloakResponseMode | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.responseMode;\n });\n\n /**\n * A signal for the keycloak-js flow property\n *\n * Flow passed in init.\n */\n flow = computed<KeycloakFlow | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.flow;\n });\n\n /**\n * A signal for the keycloak-js responseType property\n *\n * Response type sent to Keycloak with login requests. This is determined based on the flow value used during initialization,\n * but can be overridden by setting this value.\n */\n responseType = computed<KeycloakResponseType | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.responseType;\n });\n}\n","import { Component, input, inject } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { AuthService } from '../../services/auth/auth.service';\n\ntype HeaderLink = {\n text: string;\n path: string;\n};\n\n// A HeaderMenu can be either a simple link with path,\n// OR a menu group with nested items\ntype HeaderMenu = HeaderLink | {\n text: string;\n items: HeaderLink[];\n}\n\nexport type HeaderConfig = {\n title: HeaderLink;\n subtitle?: HeaderLink;\n breadcrumbs?: HeaderLink[];\n menu?: HeaderMenu[];\n}\n\n@Component({\n selector: 'byu-header',\n imports: [RouterModule],\n templateUrl: './byu-header.component.html',\n styleUrl: './byu-header.component.scss'\n})\nexport class ByuHeaderComponent {\n auth = inject(AuthService)\n config = input<HeaderConfig>();\n\n isHeaderLink(item: HeaderMenu): item is HeaderLink {\n return 'path' in item;\n }\n\n // Track which dropdown is open (null means none are open)\n openDropdownText: string | null = null;\n \n // Toggle function — if clicking the same dropdown, close it; otherwise open it\n toggleDropdown(text: string) {\n this.openDropdownText = this.openDropdownText === text ? null : text;\n }\n \n // Check if a given dropdown is currently open\n isOpen(text: string): boolean {\n return this.openDropdownText === text;\n }\n}\n","<header>\n <div class=\"top\" >\n <img class=\"logo\" src=\"/BYU_monogram_white@2x.png\" alt=\"BYU\">\n <div class=\"titles\"> \n <div class=\"breadcrumbs\">\n @for (breadcrumb of config()?.breadcrumbs; track breadcrumb.text){\n <a [href]=\"breadcrumb.path\" >{{ breadcrumb.text }}</a>\n }\n </div>\n <a [routerLink]=\"config()?.title?.path\" class=\"title\">{{ config()?.title?.text }}</a>\n <a [routerLink]=\"config()?.subtitle?.path\" class=\"subtitle\">{{ config()?.subtitle?.text }}</a>\n </div>\n <div class=\"signin\">\n <p>{{ this.auth.tokenParsed()?.['given_name'] ?? '' }}</p>\n <svg class=\"signin-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n <path fill=\"currentcolor\" d=\"M50 95c-26 0-34-18-34-18 3-12 8-18 17-18 5 5 10 7 17 7s12-2 17-7c9 0 14 6 17 18 0 0-7 18-34 18z\"></path>\n <circle cx=\"50\" cy=\"50\" r=\"45\" fill=\"none\" stroke=\"currentcolor\" stroke-width=\"10\"></circle>\n <circle fill=\"currentcolor\" cx=\"50\" cy=\"40\" r=\"20\"></circle>\n </svg>\n @if (auth.authenticated()) {\n <a class=\"signin-link\" (click)=\"auth.logout()\">Sign Out</a>\n } @else {\n <a class=\"signin-link\" (click)=\"auth.login()\">Sign In</a>\n }\n </div>\n </div>\n <div class=\"bottom\">\n <nav>\n @for (menuItem of config()?.menu; track menuItem.text){>\n @if (isHeaderLink(menuItem)) {\n <li class = \"nav-item\">\n <a class=\"nav-item-content\" [routerLink]=\"menuItem.path\">{{ menuItem.text }}</a>\n </li>\n } @else {\n <li class=\"nav-item dropdown\" (click)=\"toggleDropdown(menuItem.text)\">\n <div class=\"nav-item-content\" >{{ menuItem.text }}</div>\n @if (openDropdownText === menuItem.text) {\n <ul class = \"dropdown-item-menu\"> \n @for(dropItem of menuItem.items; track dropItem.text){\n <li class = \"dropdown-item\">\n <a class=\"dropdown-item-content\" [routerLink]=\"dropItem.path\">{{ dropItem.text }}</a>\n </li>\n }\n </ul>\n }\n </li>\n }\n }\n </nav>\n </div>\n</header>\n","import { computed, effect, signal, Signal } from '@angular/core';\n\n//\n// Generic utility types\n//\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonArray\n | JsonObject;\nexport type JsonObject = { [key: string]: JsonValue }; // this is exported because the builder's typing gets mad if it isn't\ntype JsonArray = JsonValue[];\ntype Json = JsonObject | JsonArray;\nexport type Maybe<T> = T | undefined | null;\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\ntype DefaultError = { code?: string, message?: string, details?: Json };\n\n/**\n * A value that may be a plain value of type `T` or a reactive `Signal<T>`.\n *\n * This is useful in APIs that accept either static or reactive values,\n * allowing flexibility while maintaining type safety.\n *\n * Use `unwrapSignal` to extract the underlying value in a uniform way.\n *\n * @template T The underlying value type.\n */\nexport type MaybeSignal<T> = T | Signal<T>;\n/**\n * Reads the underlying value from a MaybeSignal.\n *\n * This function is designed for use with reactive APIs (like fetchSignal) where it's important\n * that Angular's computed methods register the dependency. If the input is a Signal, invoking it \n * not only returns its current value but also tracks the signal for reactivity. If the input is \n * a plain value, it simply returns that value.\n *\n * @template T The type of the value.\n * @param value A plain value or a Signal that yields the value.\n * @returns The current value, with dependency tracking enabled if the input is a Signal.\n */\nexport function readMaybeSignal<T>(value: MaybeSignal<T>): T {\n return typeof value === 'function' ? (value as Function)() : value;\n}\n\n//\n// Fetch Request\n//\ntype FetchSignalRequest = {\n url: string;\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n headers?: Record<string, string>;\n};\n\n//\n// A function that transforms the raw fetch Response to the desired type.\n//\ntype ResponseTransformer<T> = (response: Response) => Promise<T>;\n\n/**\n * Represents the reactive result of an HTTP fetch request.\n */\nexport type FetchSignal<Response, Error = DefaultError> = {\n value: Signal<Maybe<Response>>;\n persistentValue: Signal<Maybe<Response>>;\n isLoading: Signal<boolean>;\n error: Signal<Maybe<Error>>;\n statusCode: Signal<Maybe<number>>;\n headers: Signal<Maybe<Record<string, string>>>;\n refresh: (abortSignal?: AbortSignal) => void;\n};\n\n/**\n * Creates a reactive fetch signal.\n *\n * The request function can include Signals for properties. These are unwrapped in the refresh function.\n */\nfunction createFetchSignal<Response, Error>(\n request: () => (FetchSignalRequest),\n method: HttpMethod,\n transform: ResponseTransformer<Response>,\n autoRefresh: Boolean\n): FetchSignal<Response, Error> {\n // Use a computed signal so that any changes to Signals in the request object trigger updates.\n const currentRequest = computed(request);\n\n const value = signal<Maybe<Response>>(undefined);\n const persistentValue = signal<Maybe<Response>>(undefined);\n const isLoading = signal<boolean>(false);\n const error = signal<Maybe<Error>>(undefined);\n const statusCode = signal<Maybe<number>>(undefined);\n const headers = signal<Maybe<Record<string, string>>>(undefined);\n\n const refresh = async (abortSignal?: AbortSignal) => {\n // Reset signals for a fresh request.\n value.set(undefined);\n isLoading.set(true);\n error.set(undefined);\n statusCode.set(undefined);\n headers.set(undefined);\n\n // Unwrap the current request.\n const req = currentRequest();\n const url = req.url;\n const params = req.params\n const requestHeaders = req.headers;\n const body = req.body;\n\n // Build URL with query parameters.\n let uri = url;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const key in params) {\n if (params[key] !== undefined) searchParams.append(key, String(params[key]));\n }\n uri += (uri.includes('?') ? '&' : '?') + searchParams.toString();\n }\n\n try {\n const response = await fetch(uri, {\n method,\n headers: requestHeaders,\n // Only include a body if one is provided.\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: abortSignal\n });\n\n statusCode.set(response.status);\n\n // Extract response headers.\n const headersObj: Record<string, string> = {};\n response.headers.forEach((val, key) => {\n headersObj[key] = val;\n });\n headers.set(headersObj);\n\n // if the response is ok, transform the body\n if (response.ok) {\n value.set(await transform(response));\n error.set(null);\n } else {\n // try to parse the error as json\n // set the error to null if this fails\n try { error.set(await response.json()) }\n catch { error.set(null) }\n finally { value.set(null) }\n }\n } catch (err: any) {\n // if the request is aborted, ignore the error\n if (err.name !== 'AbortError') {\n console.error(err);\n }\n error.set(null);\n value.set(null);\n } finally {\n persistentValue.set(value());\n isLoading.set(false);\n }\n };\n\n if (autoRefresh) {\n effect((onCleanup) => {\n // read the current request to trigger re-run of this effect\n currentRequest();\n\n // pass abort signal to refresh on cleanup of effect\n const controller = new AbortController();\n onCleanup(() => controller.abort());\n\n // call refresh with this abort controller\n refresh(controller.signal);\n })\n }\n\n return { value, persistentValue, isLoading, error, statusCode, headers, refresh };\n}\n\n\n//\n// The chainable API factory type.\n// Note that the default fetchSignal() (and its .json, etc.) are for GET,\n// while .post, .put, .patch require a request with a body.\n// The .delete chain is similar to GET in that no body is expected.\n//\ntype FetchSignalFactory = {\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n\n get: {\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n post: {\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n put: {\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n patch: {\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n delete: {\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n};\n\n\n//\n// Helpers for attaching response transforms for GET/DELETE (which don’t include a body).\n//\nconst createHelper = <Response, Error>(\n method: HttpMethod,\n transform: ResponseTransformer<Response>\n) => (request: () => FetchSignalRequest, autoRefresh: boolean = false): FetchSignal<Response, Error> =>\n createFetchSignal<Response, Error>(request, method, transform, autoRefresh);\n\n// Transforms\nconst jsonTransformer = (response: Response) => response.json();\nconst textTransformer = (response: Response) => response.text();\nconst blobTransformer = (response: Response) => response.blob();\nconst arrayBufferTransformer = (response: Response) => response.arrayBuffer();\n\n//\n// Build the defaults - GET chain\n//\nconst fetchSignal = createHelper('GET', jsonTransformer) as FetchSignalFactory;\nfetchSignal.json = createHelper('GET', jsonTransformer);\nfetchSignal.text = createHelper('GET', textTransformer);\nfetchSignal.blob = createHelper('GET', blobTransformer);\nfetchSignal.arrayBuffer = createHelper('GET', arrayBufferTransformer);\n\n//\n// Build the GET chain\n//\nfetchSignal.get = createHelper('GET', jsonTransformer) as FetchSignalFactory['get'];\nfetchSignal.get.json = createHelper('GET', jsonTransformer);\nfetchSignal.get.text = createHelper('GET', textTransformer);\nfetchSignal.get.blob = createHelper('GET', blobTransformer);\nfetchSignal.get.arrayBuffer = createHelper('GET', arrayBufferTransformer);\n\n//\n// Build the POST chain.\n//\nfetchSignal.post = createHelper('POST', jsonTransformer) as FetchSignalFactory['post'];\nfetchSignal.post.json = createHelper('POST', jsonTransformer);\nfetchSignal.post.text = createHelper('POST', textTransformer);\nfetchSignal.post.blob = createHelper('POST', blobTransformer);\nfetchSignal.post.arrayBuffer = createHelper('POST', arrayBufferTransformer);\n\n//\n// Build the PUT chain.\n//\nfetchSignal.put = createHelper('PUT', jsonTransformer) as FetchSignalFactory['put'];\nfetchSignal.put.json = createHelper('PUT', jsonTransformer);\nfetchSignal.put.text = createHelper('PUT', textTransformer);\nfetchSignal.put.blob = createHelper('PUT', blobTransformer);\nfetchSignal.put.arrayBuffer = createHelper('PUT', arrayBufferTransformer);\n\n//\n// Build the PATCH chain.\n//\nfetchSignal.patch = createHelper('PATCH', jsonTransformer) as FetchSignalFactory['patch'];\nfetchSignal.patch.json = createHelper('PATCH', jsonTransformer);\nfetchSignal.patch.text = createHelper('PATCH', textTransformer);\nfetchSignal.patch.blob = createHelper('PATCH', blobTransformer);\nfetchSignal.patch.arrayBuffer = createHelper('PATCH', arrayBufferTransformer);\n\n//\n// Build the DELETE chain\n//\nfetchSignal.delete = createHelper('DELETE', jsonTransformer) as FetchSignalFactory['delete'];\nfetchSignal.delete.json = createHelper('DELETE', jsonTransformer);\nfetchSignal.delete.text = createHelper('DELETE', textTransformer);\nfetchSignal.delete.blob = createHelper('DELETE', blobTransformer);\nfetchSignal.delete.arrayBuffer = createHelper('DELETE', arrayBufferTransformer);\n\nexport { fetchSignal };\n","import { inject, Injectable, Signal } from '@angular/core';\nimport { fetchSignal } from '../../signals/fetch-signal/fetch-signal';\nimport { AuthService } from '../auth/auth.service';\n\nexport type GetUsersResponse = {\n totalCount: number,\n data: {\n id: string;\n netId: string;\n accountType: string;\n preferredFirstName: string;\n preferredLastName: string;\n roles: string[];\n created: string;\n }[]\n}\n\nexport type GetUsersAccountTypes = ('NonBYU' | 'Student' | 'Employee')[]\nexport type GetUsersSortBy = 'netId' | 'accountType' | 'preferredFirstName' | 'preferredLastName' | 'created';\nexport type GetUsersSortDirection = 'asc' | 'desc' | '';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class UserManagementService {\n auth = inject(AuthService);\n\n getUsers(\n search: Signal<string>,\n accountTypes: Signal<GetUsersAccountTypes>,\n sortBy: Signal<GetUsersSortBy>,\n sortDirection: Signal<GetUsersSortDirection>,\n pageCount: Signal<number>,\n pageOffset: Signal<number>,\n createdAfter?: Signal<Date>,\n createdBefore?: Signal<Date>,\n ) {\n return fetchSignal<GetUsersResponse>(() => ({\n url: '/api/user-management',\n params: {\n search: search(),\n account_types: accountTypes().join(','),\n sort_by: sortBy(),\n sort_direction: sortDirection(),\n page_count: pageCount(),\n page_offset: pageOffset(),\n created_after: createdAfter && createdAfter().toISOString(),\n created_before: createdBefore && createdBefore().toISOString(),\n },\n headers: {\n Authorization: this.auth.bearerToken() ?? ''\n }\n }), true)\n }\n}\n","import { effect, signal, Signal } from \"@angular/core\";\n\nexport const debounced = <T>(inputSignal: Signal<T>, wait: number = 400) => {\n const debouncedSignal = signal<T>(inputSignal());\n const setSignal = debounce((value) => debouncedSignal.set(value), wait);\n\n effect(() => {\n setSignal(inputSignal())\n })\n\n return debouncedSignal;\n}\n\nconst debounce = (callback: (...args: any[]) => void, wait: number) => {\n let timeoutId: number | undefined;\n return (...args: any[]) => {\n window.clearTimeout(timeoutId);\n timeoutId = window.setTimeout(() => {\n callback(...args);\n }, wait);\n };\n}","import { Component, inject, signal, ViewChild } from '@angular/core';\nimport { GetUsersAccountTypes, GetUsersSortBy, GetUsersSortDirection, UserManagementService } from '../../services/user-management/user-management.service';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSort, MatSortModule } from '@angular/material/sort';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { debounced } from '../../signals/debounced/debounced';\n\n@Component({\n selector: 'app-user-management',\n imports: [MatTableModule, MatSortModule, MatPaginatorModule, DatePipe, FormsModule, MatInputModule, MatSelectModule],\n templateUrl: './user-management.component.html',\n styleUrl: './user-management.component.scss'\n})\nexport class UserManagementComponent {\n api = inject(UserManagementService);\n\n displayedColumns: string[] = ['netId', 'preferredFirstName', 'preferredLastName', 'roles', 'accountType', 'created'];\n accountTypeOptions = ['NonBYU', 'Student', 'Employee'];\n\n @ViewChild(MatPaginator) paginator!: MatPaginator;\n @ViewChild(MatSort) sort!: MatSort;\n\n search = signal('');\n accountTypes = signal<GetUsersAccountTypes>(['NonBYU', 'Student', 'Employee']);\n sortBy = signal<GetUsersSortBy>('netId');\n sortDirection = signal<GetUsersSortDirection>('asc');\n pageCount = signal(5);\n pageOffset = signal(0);\n\n getUsers = this.api.getUsers(\n debounced(this.search),\n this.accountTypes,\n this.sortBy,\n this.sortDirection,\n this.pageCount,\n this.pageOffset\n )\n\n ngAfterViewInit() {\n // write the observables from the sort and the paging to the signals\n this.sort.sortChange.subscribe(({active, direction}) => {\n this.sortBy.set(active as GetUsersSortBy);\n this.sortDirection.set(direction);\n this.pageOffset.set(0);\n this.paginator.pageIndex = 0;\n });\n this.paginator.page.subscribe(({pageIndex, pageSize}) => {\n this.pageOffset.set(pageIndex * pageSize)\n this.pageCount.set(pageSize)\n });\n }\n}\n","<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.persistentValue()?.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.persistentValue()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n","/**\n * Components\n */\nexport * from './lib/components/byu-footer/byu-footer.component'\nexport * from './lib/components/byu-header/byu-header.component'\nexport * from './lib/components/user-management/user-management.component'\n\n/**\n * Services\n */\nexport * from './lib/services/auth/auth.service'\n\n/**\n * Signals\n */\nexport * from './lib/signals/fetch-signal/fetch-signal'\nexport * from './lib/signals/debounced/debounced'","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1"],"mappings":"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"}
1
+ {"version":3,"file":"fhss-web-team-frontend-utils.mjs","sources":["../../../projects/frontend-utils/src/lib/components/byu-footer/byu-footer.component.ts","../../../projects/frontend-utils/src/lib/components/byu-footer/byu-footer.component.html","../../../projects/frontend-utils/src/lib/services/auth/auth.service.ts","../../../projects/frontend-utils/src/lib/components/byu-header/byu-header.component.ts","../../../projects/frontend-utils/src/lib/components/byu-header/byu-header.component.html","../../../projects/frontend-utils/src/lib/signals/debounced/debounced.ts","../../../projects/frontend-utils/src/lib/signals/fetch-signal/fetch-signal.ts","../../../projects/frontend-utils/src/lib/components/user-management/user-management.component.ts","../../../projects/frontend-utils/src/lib/components/user-management/user-management.component.html","../../../projects/frontend-utils/src/public-api.ts","../../../projects/frontend-utils/src/fhss-web-team-frontend-utils.ts"],"sourcesContent":["import { Component } from '@angular/core';\n\n@Component({\n selector: 'byu-footer',\n imports: [],\n templateUrl: './byu-footer.component.html',\n styleUrl: './byu-footer.component.scss'\n})\nexport class ByuFooterComponent {\n currentYear: number = new Date().getFullYear(); // Automatically updates the year\n}\n","<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | © {{ currentYear }} All rights reserved.</p>\n <p>\n <a href=\"https://privacy.byu.edu/privacy-notice\">Privacy Notice</a> |\n <a href=\"https://privacy.byu.edu/cookie-prefs\">Cookie Preferences</a>\n </p>\n</footer>\n \n\n","import { Injectable, inject, computed } from '@angular/core';\nimport { KEYCLOAK_EVENT_SIGNAL } from 'keycloak-angular';\nimport Keycloak, {\n KeycloakFlow,\n KeycloakLoginOptions,\n KeycloakLogoutOptions,\n KeycloakResourceAccess,\n KeycloakResponseMode,\n KeycloakResponseType,\n KeycloakRoles,\n KeycloakTokenParsed,\n} from 'keycloak-js';\n\n@Injectable({\n providedIn: 'root',\n})\n/**\n * AuthService provides a wrapper around the Keycloak JavaScript adapter, exposing its properties and methods\n * as Angular signals for reactive programming. It simplifies authentication, token management, and role-based access control.\n */\nexport class AuthService {\n private keycloak = inject(Keycloak);\n private keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);\n\n /**\n * Initiates the login process. Optionally stores a route to redirect to after login.\n * @param nextRoute - The route to navigate to after successful login.\n */\n login = (nextRoute?: string) => {\n if (nextRoute) {\n sessionStorage.setItem('nextRoute', nextRoute);\n }\n this.keycloak.login({\n redirectUri: window.location.origin + '/auth-callback',\n });\n };\n\n /**\n * Logs the user out of the application.\n * @param options - Optional logout options.\n */\n logout = (options?: KeycloakLogoutOptions) => this.keycloak.logout(options);\n\n /**\n * Signal indicating whether the user is authenticated.\n * Returns `true` if authenticated, `false` otherwise.\n */\n authenticated = computed<boolean | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.authenticated;\n });\n\n /**\n * Signal for the base64-encoded token used in the `Authorization` header.\n */\n token = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.token;\n });\n\n /**\n * Signal for the Bearer token, prefixed with \"Bearer \".\n */\n bearerToken = computed<string | undefined>(() => (this.token() ? 'Bearer ' + this.token() : undefined));\n\n /**\n * Signal for the parsed token as a JavaScript object.\n */\n tokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.tokenParsed;\n });\n\n /**\n * Signal for the user ID (Keycloak subject).\n */\n userId = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.subject;\n });\n\n /**\n * Signal for the base64-encoded ID token.\n */\n idToken = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.idToken;\n });\n\n /**\n * Signal for the parsed ID token as a JavaScript object.\n */\n idTokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.idTokenParsed;\n });\n\n /**\n * Signal for the realm roles associated with the token.\n */\n realmAccess = computed<KeycloakRoles | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.realmAccess;\n });\n\n /**\n * Signal for the resource roles associated with the token.\n */\n resourceAccess = computed<KeycloakResourceAccess | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.resourceAccess;\n });\n\n /**\n * Signal for the base64-encoded refresh token.\n */\n refreshToken = computed<string | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.refreshToken;\n });\n\n /**\n * Signal for the parsed refresh token as a JavaScript object.\n */\n refreshTokenParsed = computed<KeycloakTokenParsed | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.refreshTokenParsed;\n });\n\n /**\n * Signal for the estimated time difference between the browser and Keycloak server in seconds.\n */\n timeSkew = computed<number | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.timeSkew;\n });\n\n /**\n * Signal for the response mode passed during initialization.\n */\n responseMode = computed<KeycloakResponseMode | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.responseMode;\n });\n\n /**\n * Signal for the flow type used during initialization.\n */\n flow = computed<KeycloakFlow | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.flow;\n });\n\n /**\n * Signal for the response type sent to Keycloak during login requests.\n */\n responseType = computed<KeycloakResponseType | undefined>(() => {\n this.keycloakSignal();\n return this.keycloak.responseType;\n });\n}\n","import { Component, input, inject } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { AuthService } from '../../services/auth/auth.service';\n\ntype HeaderLink = {\n text: string;\n path: string;\n};\n\n// A HeaderMenu can be either a simple link with path,\n// OR a menu group with nested items\ntype HeaderMenu = HeaderLink | {\n text: string;\n items: HeaderLink[];\n}\n\nexport type HeaderConfig = {\n title: HeaderLink;\n subtitle?: HeaderLink;\n breadcrumbs?: HeaderLink[];\n menu?: HeaderMenu[];\n}\n\n@Component({\n selector: 'byu-header',\n imports: [RouterModule],\n templateUrl: './byu-header.component.html',\n styleUrl: './byu-header.component.scss'\n})\nexport class ByuHeaderComponent {\n auth = inject(AuthService)\n config = input<HeaderConfig>();\n\n isHeaderLink(item: HeaderMenu): item is HeaderLink {\n return 'path' in item;\n }\n\n // Track which dropdown is open (null means none are open)\n openDropdownText: string | null = null;\n \n // Toggle function — if clicking the same dropdown, close it; otherwise open it\n toggleDropdown(text: string) {\n this.openDropdownText = this.openDropdownText === text ? null : text;\n }\n \n // Check if a given dropdown is currently open\n isOpen(text: string): boolean {\n return this.openDropdownText === text;\n }\n}\n","<header>\n <div class=\"top\" >\n <img class=\"logo\" src=\"/BYU_monogram_white@2x.png\" alt=\"BYU\">\n <div class=\"titles\"> \n <div class=\"breadcrumbs\">\n @for (breadcrumb of config()?.breadcrumbs; track breadcrumb.text){\n <a [href]=\"breadcrumb.path\" >{{ breadcrumb.text }}</a>\n }\n </div>\n <a [routerLink]=\"config()?.title?.path\" class=\"title\">{{ config()?.title?.text }}</a>\n <a [routerLink]=\"config()?.subtitle?.path\" class=\"subtitle\">{{ config()?.subtitle?.text }}</a>\n </div>\n <div class=\"signin\">\n <p>{{ this.auth.tokenParsed()?.['given_name'] ?? '' }}</p>\n <svg class=\"signin-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n <path fill=\"currentcolor\" d=\"M50 95c-26 0-34-18-34-18 3-12 8-18 17-18 5 5 10 7 17 7s12-2 17-7c9 0 14 6 17 18 0 0-7 18-34 18z\"></path>\n <circle cx=\"50\" cy=\"50\" r=\"45\" fill=\"none\" stroke=\"currentcolor\" stroke-width=\"10\"></circle>\n <circle fill=\"currentcolor\" cx=\"50\" cy=\"40\" r=\"20\"></circle>\n </svg>\n @if (auth.authenticated()) {\n <a class=\"signin-link\" (click)=\"auth.logout()\">Sign Out</a>\n } @else {\n <a class=\"signin-link\" (click)=\"auth.login()\">Sign In</a>\n }\n </div>\n </div>\n <div class=\"bottom\">\n <nav>\n @for (menuItem of config()?.menu; track menuItem.text){>\n @if (isHeaderLink(menuItem)) {\n <li class = \"nav-item\">\n <a class=\"nav-item-content\" [routerLink]=\"menuItem.path\">{{ menuItem.text }}</a>\n </li>\n } @else {\n <li class=\"nav-item dropdown\" (click)=\"toggleDropdown(menuItem.text)\">\n <div class=\"nav-item-content\" >{{ menuItem.text }}</div>\n @if (openDropdownText === menuItem.text) {\n <ul class = \"dropdown-item-menu\"> \n @for(dropItem of menuItem.items; track dropItem.text){\n <li class = \"dropdown-item\">\n <a class=\"dropdown-item-content\" [routerLink]=\"dropItem.path\">{{ dropItem.text }}</a>\n </li>\n }\n </ul>\n }\n </li>\n }\n }\n </nav>\n </div>\n</header>\n","import { effect, signal, Signal } from \"@angular/core\";\n\nexport const debounced = <T>(inputSignal: Signal<T>, wait: number = 400) => {\n const debouncedSignal = signal<T>(inputSignal());\n const setSignal = debounce((value) => debouncedSignal.set(value), wait);\n\n effect(() => {\n setSignal(inputSignal())\n })\n\n return debouncedSignal;\n}\n\nconst debounce = (callback: (...args: any[]) => void, wait: number) => {\n let timeoutId: number | undefined;\n return (...args: any[]) => {\n window.clearTimeout(timeoutId);\n timeoutId = window.setTimeout(() => {\n callback(...args);\n }, wait);\n };\n}","import { computed, effect, signal, Signal } from '@angular/core';\n\n//\n// Generic utility types\n//\ntype JsonValue =\n | string\n | number\n | boolean\n | null\n | JsonArray\n | JsonObject;\nexport type JsonObject = { [key: string]: JsonValue }; // this is exported because the builder's typing gets mad if it isn't\ntype JsonArray = JsonValue[];\ntype Json = JsonObject | JsonArray;\nexport type Maybe<T> = T | undefined | null;\ntype HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\ntype DefaultError = { code?: string, message?: string, details?: Json };\n\n/**\n * A value that may be a plain value of type `T` or a reactive `Signal<T>`.\n *\n * This is useful in APIs that accept either static or reactive values,\n * allowing flexibility while maintaining type safety.\n *\n * Use `unwrapSignal` to extract the underlying value in a uniform way.\n *\n * @template T The underlying value type.\n */\nexport type MaybeSignal<T> = T | Signal<T>;\n/**\n * Reads the underlying value from a MaybeSignal.\n *\n * This function is designed for use with reactive APIs (like fetchSignal) where it's important\n * that Angular's computed methods register the dependency. If the input is a Signal, invoking it \n * not only returns its current value but also tracks the signal for reactivity. If the input is \n * a plain value, it simply returns that value.\n *\n * @template T The type of the value.\n * @param value A plain value or a Signal that yields the value.\n * @returns The current value, with dependency tracking enabled if the input is a Signal.\n */\nexport function readMaybeSignal<T>(value: MaybeSignal<T>): T {\n return typeof value === 'function' ? (value as Function)() : value;\n}\n\n//\n// Fetch Request\n//\ntype FetchSignalRequest = {\n url: string;\n body?: unknown;\n params?: Record<string, string | number | boolean | undefined>;\n headers?: Record<string, string>;\n};\n\n//\n// A function that transforms the raw fetch Response to the desired type.\n//\ntype ResponseTransformer<T> = (response: Response) => Promise<T>;\n\n/**\n * Represents the reactive result of an HTTP fetch request.\n */\nexport type FetchSignal<Response, Error = DefaultError> = {\n value: Signal<Maybe<Response>>;\n persistentValue: Signal<Maybe<Response>>;\n isLoading: Signal<boolean>;\n error: Signal<Maybe<Error>>;\n statusCode: Signal<Maybe<number>>;\n headers: Signal<Maybe<Record<string, string>>>;\n refresh: (abortSignal?: AbortSignal) => void;\n};\n\n/**\n * Creates a reactive fetch signal.\n *\n * The request function can include Signals for properties. These are unwrapped in the refresh function.\n */\nfunction createFetchSignal<Response, Error>(\n request: () => (FetchSignalRequest),\n method: HttpMethod,\n transform: ResponseTransformer<Response>,\n autoRefresh: Boolean\n): FetchSignal<Response, Error> {\n // Use a computed signal so that any changes to Signals in the request object trigger updates.\n const currentRequest = computed(request);\n\n const value = signal<Maybe<Response>>(undefined);\n const persistentValue = signal<Maybe<Response>>(undefined);\n const isLoading = signal<boolean>(false);\n const error = signal<Maybe<Error>>(undefined);\n const statusCode = signal<Maybe<number>>(undefined);\n const headers = signal<Maybe<Record<string, string>>>(undefined);\n\n const refresh = async (abortSignal?: AbortSignal) => {\n // Reset signals for a fresh request.\n value.set(undefined);\n isLoading.set(true);\n error.set(undefined);\n statusCode.set(undefined);\n headers.set(undefined);\n\n // Unwrap the current request.\n const req = currentRequest();\n const url = req.url;\n const params = req.params\n const requestHeaders = req.headers;\n const body = req.body;\n\n // Build URL with query parameters.\n let uri = url;\n if (params) {\n const searchParams = new URLSearchParams();\n for (const key in params) {\n if (params[key] !== undefined) searchParams.append(key, String(params[key]));\n }\n uri += (uri.includes('?') ? '&' : '?') + searchParams.toString();\n }\n\n try {\n const response = await fetch(uri, {\n method,\n headers: requestHeaders,\n // Only include a body if one is provided.\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: abortSignal\n });\n\n statusCode.set(response.status);\n\n // Extract response headers.\n const headersObj: Record<string, string> = {};\n response.headers.forEach((val, key) => {\n headersObj[key] = val;\n });\n headers.set(headersObj);\n\n // if the response is ok, transform the body\n if (response.ok) {\n value.set(await transform(response));\n error.set(null);\n } else {\n // try to parse the error as json\n // set the error to null if this fails\n try { error.set(await response.json()) }\n catch { error.set(null) }\n finally { value.set(null) }\n }\n } catch (err: any) {\n // if the request is aborted, ignore the error\n if (err.name !== 'AbortError') {\n console.error(err);\n }\n error.set(null);\n value.set(null);\n } finally {\n persistentValue.set(value());\n isLoading.set(false);\n }\n };\n\n if (autoRefresh) {\n effect((onCleanup) => {\n // read the current request to trigger re-run of this effect\n currentRequest();\n\n // pass abort signal to refresh on cleanup of effect\n const controller = new AbortController();\n onCleanup(() => controller.abort());\n\n // call refresh with this abort controller\n refresh(controller.signal);\n })\n }\n\n return { value, persistentValue, isLoading, error, statusCode, headers, refresh };\n}\n\n\n//\n// The chainable API factory type.\n// Note that the default fetchSignal() (and its .json, etc.) are for GET,\n// while .post, .put, .patch require a request with a body.\n// The .delete chain is similar to GET in that no body is expected.\n//\ntype FetchSignalFactory = {\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n\n get: {\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP GET request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP GET request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n post: {\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP POST request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP POST request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n put: {\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP PUT request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PUT request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n patch: {\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP PATCH request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP PATCH request, which must include a body.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n\n delete: {\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object containing reactive signals for the response data, loading state, status code, error, headers, and a refresh method.\n */\n <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean): FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as JSON.\n *\n * @template Response - The expected response type.\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the JSON-parsed response.\n */\n json: <Response extends Json, Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Response, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as plain text.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the text response.\n */\n text: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<string, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as a Blob.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the Blob response.\n */\n blob: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<Blob, Error>;\n\n /**\n * Initiates a reactive HTTP DELETE request using the provided request configuration and parses the response as an ArrayBuffer.\n *\n * @template Error - The expected error shape (defaults to `DefaultError`).\n * @param request - The configuration object for the HTTP DELETE request.\n * @param autoRefresh - whether or not the request should be automatically refreshed when the request input signals have changed value\n * @returns A `FetchSignal` object with the ArrayBuffer response.\n */\n arrayBuffer: <Error extends Json = DefaultError>(request: () => FetchSignalRequest, autoRefresh?: boolean) => FetchSignal<ArrayBuffer, Error>;\n };\n};\n\n\n//\n// Helpers for attaching response transforms for GET/DELETE (which don’t include a body).\n//\nconst createHelper = <Response, Error>(\n method: HttpMethod,\n transform: ResponseTransformer<Response>\n) => (request: () => FetchSignalRequest, autoRefresh: boolean = false): FetchSignal<Response, Error> =>\n createFetchSignal<Response, Error>(request, method, transform, autoRefresh);\n\n// Transforms\nconst jsonTransformer = (response: Response) => response.json();\nconst textTransformer = (response: Response) => response.text();\nconst blobTransformer = (response: Response) => response.blob();\nconst arrayBufferTransformer = (response: Response) => response.arrayBuffer();\n\n//\n// Build the defaults - GET chain\n//\nconst fetchSignal = createHelper('GET', jsonTransformer) as FetchSignalFactory;\nfetchSignal.json = createHelper('GET', jsonTransformer);\nfetchSignal.text = createHelper('GET', textTransformer);\nfetchSignal.blob = createHelper('GET', blobTransformer);\nfetchSignal.arrayBuffer = createHelper('GET', arrayBufferTransformer);\n\n//\n// Build the GET chain\n//\nfetchSignal.get = createHelper('GET', jsonTransformer) as FetchSignalFactory['get'];\nfetchSignal.get.json = createHelper('GET', jsonTransformer);\nfetchSignal.get.text = createHelper('GET', textTransformer);\nfetchSignal.get.blob = createHelper('GET', blobTransformer);\nfetchSignal.get.arrayBuffer = createHelper('GET', arrayBufferTransformer);\n\n//\n// Build the POST chain.\n//\nfetchSignal.post = createHelper('POST', jsonTransformer) as FetchSignalFactory['post'];\nfetchSignal.post.json = createHelper('POST', jsonTransformer);\nfetchSignal.post.text = createHelper('POST', textTransformer);\nfetchSignal.post.blob = createHelper('POST', blobTransformer);\nfetchSignal.post.arrayBuffer = createHelper('POST', arrayBufferTransformer);\n\n//\n// Build the PUT chain.\n//\nfetchSignal.put = createHelper('PUT', jsonTransformer) as FetchSignalFactory['put'];\nfetchSignal.put.json = createHelper('PUT', jsonTransformer);\nfetchSignal.put.text = createHelper('PUT', textTransformer);\nfetchSignal.put.blob = createHelper('PUT', blobTransformer);\nfetchSignal.put.arrayBuffer = createHelper('PUT', arrayBufferTransformer);\n\n//\n// Build the PATCH chain.\n//\nfetchSignal.patch = createHelper('PATCH', jsonTransformer) as FetchSignalFactory['patch'];\nfetchSignal.patch.json = createHelper('PATCH', jsonTransformer);\nfetchSignal.patch.text = createHelper('PATCH', textTransformer);\nfetchSignal.patch.blob = createHelper('PATCH', blobTransformer);\nfetchSignal.patch.arrayBuffer = createHelper('PATCH', arrayBufferTransformer);\n\n//\n// Build the DELETE chain\n//\nfetchSignal.delete = createHelper('DELETE', jsonTransformer) as FetchSignalFactory['delete'];\nfetchSignal.delete.json = createHelper('DELETE', jsonTransformer);\nfetchSignal.delete.text = createHelper('DELETE', textTransformer);\nfetchSignal.delete.blob = createHelper('DELETE', blobTransformer);\nfetchSignal.delete.arrayBuffer = createHelper('DELETE', arrayBufferTransformer);\n\nexport { fetchSignal };\n","import { Component, inject, Signal, signal, ViewChild } from '@angular/core';\nimport { MatTableModule } from '@angular/material/table';\nimport { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';\nimport { MatSort, MatSortModule } from '@angular/material/sort';\nimport { DatePipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatSelectModule } from '@angular/material/select';\nimport { debounced } from '../../signals/debounced/debounced';\nimport { AuthService } from '../../services/auth/auth.service';\nimport { fetchSignal } from '../../signals/fetch-signal/fetch-signal';\nimport { GetUsersAccountTypes, GetUsersResponse, GetUsersSortBy, GetUsersSortDirection } from './user-management.types';\n\n@Component({\n selector: 'app-user-management',\n imports: [\n MatTableModule,\n MatSortModule,\n MatPaginatorModule,\n DatePipe,\n FormsModule,\n MatInputModule,\n MatSelectModule,\n ],\n templateUrl: './user-management.component.html',\n styleUrl: './user-management.component.scss',\n})\nexport class UserManagementComponent {\n auth = inject(AuthService);\n\n displayedColumns: string[] = [\n 'netId',\n 'preferredFirstName',\n 'preferredLastName',\n 'roles',\n 'accountType',\n 'created',\n ];\n accountTypeOptions = ['NonBYU', 'Student', 'Employee'];\n\n @ViewChild(MatPaginator) paginator!: MatPaginator;\n @ViewChild(MatSort) sort!: MatSort;\n\n search = signal('');\n accountTypes = signal<GetUsersAccountTypes>([\n 'NonBYU',\n 'Student',\n 'Employee',\n ]);\n sortBy = signal<GetUsersSortBy>('netId');\n sortDirection = signal<GetUsersSortDirection>('asc');\n pageCount = signal(5);\n pageOffset = signal(0);\n\n getUsers = fetchSignal<GetUsersResponse>(\n () => ({\n url: '/api/user-management',\n params: {\n search: debounced(this.search)(),\n account_types: this.accountTypes().join(','),\n sort_by: this.sortBy(),\n sort_direction: this.sortDirection(),\n page_count: this.pageCount(),\n page_offset: this.pageOffset(),\n // created_after: this.createdAfter().toISOString(),\n // created_before: this.createdBefore().toISOString(),\n },\n headers: {\n Authorization: this.auth.bearerToken() ?? '',\n },\n }),\n true\n );\n\n ngAfterViewInit() {\n // write the observables from the sort and the paging to the signals\n this.sort.sortChange.subscribe(({ active, direction }) => {\n this.sortBy.set(active as GetUsersSortBy);\n this.sortDirection.set(direction);\n this.pageOffset.set(0);\n this.paginator.pageIndex = 0;\n });\n this.paginator.page.subscribe(({ pageIndex, pageSize }) => {\n this.pageOffset.set(pageIndex * pageSize);\n this.pageCount.set(pageSize);\n });\n }\n}\n","<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.persistentValue()?.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.persistentValue()?.totalCount\" [pageSize]=\"pageCount()\" [pageSizeOptions]=\"[5, 10, 20]\" showFirstLastButtons></mat-paginator>\n","/**\n * Components\n */\nexport * from './lib/components/byu-footer/byu-footer.component'\nexport * from './lib/components/byu-header/byu-header.component'\nexport * from './lib/components/user-management/user-management.component'\n\n/**\n * Services\n */\nexport * from './lib/services/auth/auth.service'\n\n/**\n * Signals\n */\nexport * from './lib/signals/fetch-signal/fetch-signal'\nexport * from './lib/signals/debounced/debounced'","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
- import { GetUsersAccountTypes, GetUsersSortBy, GetUsersSortDirection, UserManagementService } from '../../services/user-management/user-management.service';
2
1
  import { MatPaginator } from '@angular/material/paginator';
3
2
  import { MatSort } from '@angular/material/sort';
3
+ import { AuthService } from '../../services/auth/auth.service';
4
+ import { GetUsersAccountTypes, GetUsersResponse, GetUsersSortBy, GetUsersSortDirection } from './user-management.types';
4
5
  import * as i0 from "@angular/core";
5
6
  export declare class UserManagementComponent {
6
- api: UserManagementService;
7
+ auth: AuthService;
7
8
  displayedColumns: string[];
8
9
  accountTypeOptions: string[];
9
10
  paginator: MatPaginator;
@@ -14,7 +15,7 @@ export declare class UserManagementComponent {
14
15
  sortDirection: import("@angular/core").WritableSignal<GetUsersSortDirection>;
15
16
  pageCount: import("@angular/core").WritableSignal<number>;
16
17
  pageOffset: import("@angular/core").WritableSignal<number>;
17
- getUsers: import("@fhss-web-team/frontend-utils").FetchSignal<import("../../services/user-management/user-management.service").GetUsersResponse, {
18
+ getUsers: import("@fhss-web-team/frontend-utils").FetchSignal<GetUsersResponse, {
18
19
  code?: string;
19
20
  message?: string;
20
21
  details?: (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject;
@@ -0,0 +1,15 @@
1
+ export type GetUsersResponse = {
2
+ totalCount: number;
3
+ data: {
4
+ id: string;
5
+ netId: string;
6
+ accountType: string;
7
+ preferredFirstName: string;
8
+ preferredLastName: string;
9
+ roles: string[];
10
+ created: string;
11
+ }[];
12
+ };
13
+ export type GetUsersAccountTypes = ('NonBYU' | 'Student' | 'Employee')[];
14
+ export type GetUsersSortBy = 'netId' | 'accountType' | 'preferredFirstName' | 'preferredLastName' | 'created';
15
+ export type GetUsersSortDirection = 'asc' | 'desc' | '';
@@ -3,96 +3,75 @@ import * as i0 from "@angular/core";
3
3
  export declare class AuthService {
4
4
  private keycloak;
5
5
  private keycloakSignal;
6
+ /**
7
+ * Initiates the login process. Optionally stores a route to redirect to after login.
8
+ * @param nextRoute - The route to navigate to after successful login.
9
+ */
6
10
  login: (nextRoute?: string) => void;
11
+ /**
12
+ * Logs the user out of the application.
13
+ * @param options - Optional logout options.
14
+ */
7
15
  logout: (options?: KeycloakLogoutOptions) => Promise<void>;
8
16
  /**
9
- * A signal for the keycloak-js authenticated property
10
- *
11
- * Is `true` if the user is authenticated, `false` otherwise.
17
+ * Signal indicating whether the user is authenticated.
18
+ * Returns `true` if authenticated, `false` otherwise.
12
19
  */
13
20
  authenticated: import("@angular/core").Signal<boolean | undefined>;
14
21
  /**
15
- * A signal for the keycloak-js token property
16
- *
17
- * The base64 encoded token that can be sent in the `Authorization` header in requests to services.
22
+ * Signal for the base64-encoded token used in the `Authorization` header.
18
23
  */
19
24
  token: import("@angular/core").Signal<string | undefined>;
20
25
  /**
21
- * A signal that will return the Bearer Token for use in the Authorization header
26
+ * Signal for the Bearer token, prefixed with "Bearer ".
22
27
  */
23
28
  bearerToken: import("@angular/core").Signal<string | undefined>;
24
29
  /**
25
- * A signal for the keycloak-js tokenParsed property
26
- *
27
- * The parsed token as a JavaScript object.
30
+ * Signal for the parsed token as a JavaScript object.
28
31
  */
29
32
  tokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
30
33
  /**
31
- * A signal for the keycloak-js subject property
32
- *
33
- * The user id.
34
+ * Signal for the user ID (Keycloak subject).
34
35
  */
35
36
  userId: import("@angular/core").Signal<string | undefined>;
36
37
  /**
37
- * A signal for the keycloak-js idToken property
38
- *
39
- * The base64 encoded ID token.
38
+ * Signal for the base64-encoded ID token.
40
39
  */
41
40
  idToken: import("@angular/core").Signal<string | undefined>;
42
41
  /**
43
- * A signal for the keycloak-js idTokenParsed property
44
- *
45
- * The parsed id token as a JavaScript object.
42
+ * Signal for the parsed ID token as a JavaScript object.
46
43
  */
47
44
  idTokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
48
45
  /**
49
- * A signal for the keycloak-js realmAccess property
50
- *
51
- * The realm roles associated with the token.
46
+ * Signal for the realm roles associated with the token.
52
47
  */
53
48
  realmAccess: import("@angular/core").Signal<KeycloakRoles | undefined>;
54
49
  /**
55
- * A signal for the keycloak-js resourceAccess property
56
- *
57
- * The resource roles associated with the token.
50
+ * Signal for the resource roles associated with the token.
58
51
  */
59
52
  resourceAccess: import("@angular/core").Signal<KeycloakResourceAccess | undefined>;
60
53
  /**
61
- * A signal for the keycloak-js refreshToken property
62
- *
63
- * The base64 encoded refresh token that can be used to retrieve a new token.
54
+ * Signal for the base64-encoded refresh token.
64
55
  */
65
56
  refreshToken: import("@angular/core").Signal<string | undefined>;
66
57
  /**
67
- * A signal for the keycloak-js refreshTokenParsed property
68
- *
69
- * The parsed refresh token as a JavaScript object.
58
+ * Signal for the parsed refresh token as a JavaScript object.
70
59
  */
71
60
  refreshTokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
72
61
  /**
73
- * A signal for the keycloak-js timeSkew property
74
- *
75
- * The estimated time difference between the browser time and the Keycloak server in seconds.
76
- * This value is just an estimation, but is accurate enough when determining if a token is expired or not.
62
+ * Signal for the estimated time difference between the browser and Keycloak server in seconds.
77
63
  */
78
64
  timeSkew: import("@angular/core").Signal<number | undefined>;
79
65
  /**
80
- * A signal for the keycloak-js responseMode property
81
- *
82
- * Response mode passed in init (default value is fragment).
66
+ * Signal for the response mode passed during initialization.
83
67
  */
84
68
  responseMode: import("@angular/core").Signal<KeycloakResponseMode | undefined>;
85
69
  /**
86
- * A signal for the keycloak-js flow property
87
- *
88
- * Flow passed in init.
70
+ * Signal for the flow type used during initialization.
89
71
  */
90
72
  flow: import("@angular/core").Signal<KeycloakFlow | undefined>;
91
73
  /**
92
- * A signal for the keycloak-js responseType property
93
- *
94
- * Response type sent to Keycloak with login requests. This is determined based on the flow value used during initialization,
95
- * but can be overridden by setting this value.
74
+ * Signal for the response type sent to Keycloak during login requests.
96
75
  */
97
76
  responseType: import("@angular/core").Signal<KeycloakResponseType | undefined>;
98
77
  static ɵfac: i0.ɵɵFactoryDeclaration<AuthService, never>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fhss-web-team/frontend-utils",
3
- "version": "0.0.7",
3
+ "version": "1.0.1",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^19.2.0",
6
6
  "@angular/core": "^19.2.0"
@@ -1,28 +0,0 @@
1
- import { Signal } from '@angular/core';
2
- import { AuthService } from '../auth/auth.service';
3
- import * as i0 from "@angular/core";
4
- export type GetUsersResponse = {
5
- totalCount: number;
6
- data: {
7
- id: string;
8
- netId: string;
9
- accountType: string;
10
- preferredFirstName: string;
11
- preferredLastName: string;
12
- roles: string[];
13
- created: string;
14
- }[];
15
- };
16
- export type GetUsersAccountTypes = ('NonBYU' | 'Student' | 'Employee')[];
17
- export type GetUsersSortBy = 'netId' | 'accountType' | 'preferredFirstName' | 'preferredLastName' | 'created';
18
- export type GetUsersSortDirection = 'asc' | 'desc' | '';
19
- export declare class UserManagementService {
20
- auth: AuthService;
21
- getUsers(search: Signal<string>, accountTypes: Signal<GetUsersAccountTypes>, sortBy: Signal<GetUsersSortBy>, sortDirection: Signal<GetUsersSortDirection>, pageCount: Signal<number>, pageOffset: Signal<number>, createdAfter?: Signal<Date>, createdBefore?: Signal<Date>): import("@fhss-web-team/frontend-utils").FetchSignal<GetUsersResponse, {
22
- code?: string;
23
- message?: string;
24
- details?: (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject | null)[] | import("@fhss-web-team/frontend-utils").JsonObject;
25
- }>;
26
- static ɵfac: i0.ɵɵFactoryDeclaration<UserManagementService, never>;
27
- static ɵprov: i0.ɵɵInjectableDeclaration<UserManagementService>;
28
- }