@fhss-web-team/frontend-utils 0.0.2 → 0.0.4

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,2 +1,63 @@
1
- # frontend-utils
2
- Utility code for the frontend of FHSS Web applications.
1
+ # FrontendUtils
2
+
3
+ This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.0.
4
+
5
+ ## Code scaffolding
6
+
7
+ Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
8
+
9
+ ```bash
10
+ ng generate component component-name
11
+ ```
12
+
13
+ For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
+
15
+ ```bash
16
+ ng generate --help
17
+ ```
18
+
19
+ ## Building
20
+
21
+ To build the library, run:
22
+
23
+ ```bash
24
+ ng build frontend-utils
25
+ ```
26
+
27
+ This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
28
+
29
+ ### Publishing the Library
30
+
31
+ Once the project is built, you can publish your library by following these steps:
32
+
33
+ 1. Navigate to the `dist` directory:
34
+ ```bash
35
+ cd dist/frontend-utils
36
+ ```
37
+
38
+ 2. Run the `npm publish` command to publish your library to the npm registry:
39
+ ```bash
40
+ npm publish
41
+ ```
42
+
43
+ ## Running unit tests
44
+
45
+ To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
46
+
47
+ ```bash
48
+ ng test
49
+ ```
50
+
51
+ ## Running end-to-end tests
52
+
53
+ For end-to-end (e2e) testing, run:
54
+
55
+ ```bash
56
+ ng e2e
57
+ ```
58
+
59
+ Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
60
+
61
+ ## Additional Resources
62
+
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.
@@ -0,0 +1,398 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, inject, computed, Injectable, input, signal, effect } from '@angular/core';
3
+ import * as i1 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import { KEYCLOAK_EVENT_SIGNAL } from 'keycloak-angular';
6
+ import Keycloak from 'keycloak-js';
7
+
8
+ class ByuFooterComponent {
9
+ currentYear = new Date().getFullYear(); // Automatically updates the year
10
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
11
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.8", type: ByuFooterComponent, isStandalone: true, selector: "byu-footer", ngImport: i0, template: "<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | \u00A9 {{ 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", styles: ["footer{font-family:HCo Ringside Narrow SSm,Open Sans,Helvetica,Arial,sans-serif;font-weight:400;font-size:14px;color:#fff;background-color:#002e5d;text-align:center;padding:10px 20px}p{margin:8px 0}a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}.title{font-family:HCo Ringside Narrow SSm Bold,Open Sans,Helvetica,Arial,sans-serif;font-weight:700;font-size:20px;letter-spacing:5px;margin-bottom:18px}.title a:hover{text-decoration:none}\n"] });
12
+ }
13
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuFooterComponent, decorators: [{
14
+ type: Component,
15
+ args: [{ selector: 'byu-footer', imports: [], template: "<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | \u00A9 {{ 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", styles: ["footer{font-family:HCo Ringside Narrow SSm,Open Sans,Helvetica,Arial,sans-serif;font-weight:400;font-size:14px;color:#fff;background-color:#002e5d;text-align:center;padding:10px 20px}p{margin:8px 0}a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}.title{font-family:HCo Ringside Narrow SSm Bold,Open Sans,Helvetica,Arial,sans-serif;font-weight:700;font-size:20px;letter-spacing:5px;margin-bottom:18px}.title a:hover{text-decoration:none}\n"] }]
16
+ }] });
17
+
18
+ /**
19
+ * FIXME: Finish the wrapper for all properties and methods
20
+ *
21
+ * See https://www.keycloak.org/securing-apps/javascript-adapter
22
+ */
23
+ class AuthService {
24
+ keycloak = inject(Keycloak);
25
+ keycloakSignal = inject(KEYCLOAK_EVENT_SIGNAL);
26
+ login = (nextRoute) => {
27
+ if (nextRoute) {
28
+ sessionStorage.setItem('nextRoute', nextRoute);
29
+ }
30
+ this.keycloak.login({
31
+ redirectUri: window.location.origin + '/auth-callback',
32
+ });
33
+ };
34
+ logout = (options) => this.keycloak.logout(options);
35
+ /**
36
+ * A signal for the keycloak-js authenticated property
37
+ *
38
+ * Is `true` if the user is authenticated, `false` otherwise.
39
+ */
40
+ authenticated = computed(() => {
41
+ this.keycloakSignal();
42
+ return this.keycloak.authenticated;
43
+ });
44
+ /**
45
+ * A signal for the keycloak-js token property
46
+ *
47
+ * The base64 encoded token that can be sent in the `Authorization` header in requests to services.
48
+ */
49
+ token = computed(() => {
50
+ this.keycloakSignal();
51
+ return this.keycloak.token;
52
+ });
53
+ /**
54
+ * A signal that will return the Bearer Token for use in the Authorization header
55
+ */
56
+ bearerToken = computed(() => (this.token() ? 'Bearer ' + this.token() : undefined));
57
+ /**
58
+ * A signal for the keycloak-js tokenParsed property
59
+ *
60
+ * The parsed token as a JavaScript object.
61
+ */
62
+ tokenParsed = computed(() => {
63
+ this.keycloakSignal();
64
+ return this.keycloak.tokenParsed;
65
+ });
66
+ /**
67
+ * A signal for the keycloak-js subject property
68
+ *
69
+ * The user id.
70
+ */
71
+ userId = computed(() => {
72
+ this.keycloakSignal();
73
+ return this.keycloak.subject;
74
+ });
75
+ /**
76
+ * A signal for the keycloak-js idToken property
77
+ *
78
+ * The base64 encoded ID token.
79
+ */
80
+ idToken = computed(() => {
81
+ this.keycloakSignal();
82
+ return this.keycloak.idToken;
83
+ });
84
+ /**
85
+ * A signal for the keycloak-js idTokenParsed property
86
+ *
87
+ * The parsed id token as a JavaScript object.
88
+ */
89
+ idTokenParsed = computed(() => {
90
+ this.keycloakSignal();
91
+ return this.keycloak.idTokenParsed;
92
+ });
93
+ /**
94
+ * A signal for the keycloak-js realmAccess property
95
+ *
96
+ * The realm roles associated with the token.
97
+ */
98
+ realmAccess = computed(() => {
99
+ this.keycloakSignal();
100
+ return this.keycloak.realmAccess;
101
+ });
102
+ /**
103
+ * A signal for the keycloak-js resourceAccess property
104
+ *
105
+ * The resource roles associated with the token.
106
+ */
107
+ resourceAccess = computed(() => {
108
+ this.keycloakSignal();
109
+ return this.keycloak.resourceAccess;
110
+ });
111
+ /**
112
+ * A signal for the keycloak-js refreshToken property
113
+ *
114
+ * The base64 encoded refresh token that can be used to retrieve a new token.
115
+ */
116
+ refreshToken = computed(() => {
117
+ this.keycloakSignal();
118
+ return this.keycloak.refreshToken;
119
+ });
120
+ /**
121
+ * A signal for the keycloak-js refreshTokenParsed property
122
+ *
123
+ * The parsed refresh token as a JavaScript object.
124
+ */
125
+ refreshTokenParsed = computed(() => {
126
+ this.keycloakSignal();
127
+ return this.keycloak.refreshTokenParsed;
128
+ });
129
+ /**
130
+ * A signal for the keycloak-js timeSkew property
131
+ *
132
+ * The estimated time difference between the browser time and the Keycloak server in seconds.
133
+ * This value is just an estimation, but is accurate enough when determining if a token is expired or not.
134
+ */
135
+ timeSkew = computed(() => {
136
+ this.keycloakSignal();
137
+ return this.keycloak.timeSkew;
138
+ });
139
+ /**
140
+ * A signal for the keycloak-js responseMode property
141
+ *
142
+ * Response mode passed in init (default value is fragment).
143
+ */
144
+ responseMode = computed(() => {
145
+ this.keycloakSignal();
146
+ return this.keycloak.responseMode;
147
+ });
148
+ /**
149
+ * A signal for the keycloak-js flow property
150
+ *
151
+ * Flow passed in init.
152
+ */
153
+ flow = computed(() => {
154
+ this.keycloakSignal();
155
+ return this.keycloak.flow;
156
+ });
157
+ /**
158
+ * A signal for the keycloak-js responseType property
159
+ *
160
+ * Response type sent to Keycloak with login requests. This is determined based on the flow value used during initialization,
161
+ * but can be overridden by setting this value.
162
+ */
163
+ responseType = computed(() => {
164
+ this.keycloakSignal();
165
+ return this.keycloak.responseType;
166
+ });
167
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
168
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AuthService, providedIn: 'root' });
169
+ }
170
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: AuthService, decorators: [{
171
+ type: Injectable,
172
+ args: [{
173
+ providedIn: 'root',
174
+ }]
175
+ }] });
176
+
177
+ class ByuHeaderComponent {
178
+ auth = inject(AuthService);
179
+ config = input();
180
+ isHeaderLink(item) {
181
+ return 'path' in item;
182
+ }
183
+ // Track which dropdown is open (null means none are open)
184
+ openDropdownText = null;
185
+ // Toggle function — if clicking the same dropdown, close it; otherwise open it
186
+ toggleDropdown(text) {
187
+ this.openDropdownText = this.openDropdownText === text ? null : text;
188
+ }
189
+ // Check if a given dropdown is currently open
190
+ isOpen(text) {
191
+ return this.openDropdownText === text;
192
+ }
193
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
194
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.8", type: ByuHeaderComponent, isStandalone: true, selector: "byu-header", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, 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"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
195
+ }
196
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuHeaderComponent, decorators: [{
197
+ type: Component,
198
+ 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"] }]
199
+ }] });
200
+
201
+ /**
202
+ * Reads the underlying value from a MaybeSignal.
203
+ *
204
+ * This function is designed for use with reactive APIs (like fetchSignal) where it's important
205
+ * that Angular's computed methods register the dependency. If the input is a Signal, invoking it
206
+ * not only returns its current value but also tracks the signal for reactivity. If the input is
207
+ * a plain value, it simply returns that value.
208
+ *
209
+ * @template T The type of the value.
210
+ * @param value A plain value or a Signal that yields the value.
211
+ * @returns The current value, with dependency tracking enabled if the input is a Signal.
212
+ */
213
+ function readMaybeSignal(value) {
214
+ return typeof value === 'function' ? value() : value;
215
+ }
216
+ /**
217
+ * Creates a reactive fetch signal.
218
+ *
219
+ * The request function can include Signals for properties. These are unwrapped in the refresh function.
220
+ */
221
+ function createFetchSignal(request, method, transform, autoRefresh) {
222
+ // Use a computed signal so that any changes to Signals in the request object trigger updates.
223
+ const currentRequest = computed(request);
224
+ const value = signal(undefined);
225
+ const persistentValue = signal(undefined);
226
+ const isLoading = signal(false);
227
+ const error = signal(undefined);
228
+ const statusCode = signal(undefined);
229
+ const headers = signal(undefined);
230
+ const refresh = async (abortSignal) => {
231
+ // Reset signals for a fresh request.
232
+ value.set(undefined);
233
+ isLoading.set(true);
234
+ error.set(undefined);
235
+ statusCode.set(undefined);
236
+ headers.set(undefined);
237
+ // Unwrap the current request.
238
+ const req = currentRequest();
239
+ const url = req.url;
240
+ const params = req.params;
241
+ const requestHeaders = req.headers;
242
+ const body = req.body;
243
+ // Build URL with query parameters.
244
+ let uri = url;
245
+ if (params) {
246
+ const searchParams = new URLSearchParams();
247
+ for (const key in params) {
248
+ if (params[key] !== undefined)
249
+ searchParams.append(key, String(params[key]));
250
+ }
251
+ uri += (uri.includes('?') ? '&' : '?') + searchParams.toString();
252
+ }
253
+ try {
254
+ const response = await fetch(uri, {
255
+ method,
256
+ headers: requestHeaders,
257
+ // Only include a body if one is provided.
258
+ body: body !== undefined ? JSON.stringify(body) : undefined,
259
+ signal: abortSignal
260
+ });
261
+ statusCode.set(response.status);
262
+ // Extract response headers.
263
+ const headersObj = {};
264
+ response.headers.forEach((val, key) => {
265
+ headersObj[key] = val;
266
+ });
267
+ headers.set(headersObj);
268
+ // if the response is ok, transform the body
269
+ if (response.ok) {
270
+ value.set(await transform(response));
271
+ error.set(null);
272
+ }
273
+ else {
274
+ // try to parse the error as json
275
+ // set the error to null if this fails
276
+ try {
277
+ error.set(await response.json());
278
+ }
279
+ catch {
280
+ error.set(null);
281
+ }
282
+ finally {
283
+ value.set(null);
284
+ }
285
+ }
286
+ }
287
+ catch (err) {
288
+ // if the request is aborted, ignore the error
289
+ if (err.name !== 'AbortError') {
290
+ console.error(err);
291
+ }
292
+ error.set(null);
293
+ value.set(null);
294
+ }
295
+ finally {
296
+ persistentValue.set(value());
297
+ isLoading.set(false);
298
+ }
299
+ };
300
+ if (autoRefresh) {
301
+ effect((onCleanup) => {
302
+ // read the current request to trigger re-run of this effect
303
+ currentRequest();
304
+ // pass abort signal to refresh on cleanup of effect
305
+ const controller = new AbortController();
306
+ onCleanup(() => controller.abort());
307
+ // call refresh with this abort controller
308
+ refresh(controller.signal);
309
+ });
310
+ }
311
+ return { value, persistentValue, isLoading, error, statusCode, headers, refresh };
312
+ }
313
+ //
314
+ // Helpers for attaching response transforms for GET/DELETE (which don’t include a body).
315
+ //
316
+ const createHelper = (method, transform) => (request, autoRefresh = false) => createFetchSignal(request, method, transform, autoRefresh);
317
+ // Transforms
318
+ const jsonTransformer = (response) => response.json();
319
+ const textTransformer = (response) => response.text();
320
+ const blobTransformer = (response) => response.blob();
321
+ const arrayBufferTransformer = (response) => response.arrayBuffer();
322
+ //
323
+ // Build the defaults - GET chain
324
+ //
325
+ const fetchSignal = createHelper('GET', jsonTransformer);
326
+ fetchSignal.json = createHelper('GET', jsonTransformer);
327
+ fetchSignal.text = createHelper('GET', textTransformer);
328
+ fetchSignal.blob = createHelper('GET', blobTransformer);
329
+ fetchSignal.arrayBuffer = createHelper('GET', arrayBufferTransformer);
330
+ //
331
+ // Build the GET chain
332
+ //
333
+ fetchSignal.get = createHelper('GET', jsonTransformer);
334
+ fetchSignal.get.json = createHelper('GET', jsonTransformer);
335
+ fetchSignal.get.text = createHelper('GET', textTransformer);
336
+ fetchSignal.get.blob = createHelper('GET', blobTransformer);
337
+ fetchSignal.get.arrayBuffer = createHelper('GET', arrayBufferTransformer);
338
+ //
339
+ // Build the POST chain.
340
+ //
341
+ fetchSignal.post = createHelper('POST', jsonTransformer);
342
+ fetchSignal.post.json = createHelper('POST', jsonTransformer);
343
+ fetchSignal.post.text = createHelper('POST', textTransformer);
344
+ fetchSignal.post.blob = createHelper('POST', blobTransformer);
345
+ fetchSignal.post.arrayBuffer = createHelper('POST', arrayBufferTransformer);
346
+ //
347
+ // Build the PUT chain.
348
+ //
349
+ fetchSignal.put = createHelper('PUT', jsonTransformer);
350
+ fetchSignal.put.json = createHelper('PUT', jsonTransformer);
351
+ fetchSignal.put.text = createHelper('PUT', textTransformer);
352
+ fetchSignal.put.blob = createHelper('PUT', blobTransformer);
353
+ fetchSignal.put.arrayBuffer = createHelper('PUT', arrayBufferTransformer);
354
+ //
355
+ // Build the PATCH chain.
356
+ //
357
+ fetchSignal.patch = createHelper('PATCH', jsonTransformer);
358
+ fetchSignal.patch.json = createHelper('PATCH', jsonTransformer);
359
+ fetchSignal.patch.text = createHelper('PATCH', textTransformer);
360
+ fetchSignal.patch.blob = createHelper('PATCH', blobTransformer);
361
+ fetchSignal.patch.arrayBuffer = createHelper('PATCH', arrayBufferTransformer);
362
+ //
363
+ // Build the DELETE chain
364
+ //
365
+ fetchSignal.delete = createHelper('DELETE', jsonTransformer);
366
+ fetchSignal.delete.json = createHelper('DELETE', jsonTransformer);
367
+ fetchSignal.delete.text = createHelper('DELETE', textTransformer);
368
+ fetchSignal.delete.blob = createHelper('DELETE', blobTransformer);
369
+ fetchSignal.delete.arrayBuffer = createHelper('DELETE', arrayBufferTransformer);
370
+
371
+ const debounced = (inputSignal, wait = 400) => {
372
+ const debouncedSignal = signal(inputSignal());
373
+ const setSignal = debounce((value) => debouncedSignal.set(value), wait);
374
+ effect(() => {
375
+ setSignal(inputSignal());
376
+ });
377
+ return debouncedSignal;
378
+ };
379
+ const debounce = (callback, wait) => {
380
+ let timeoutId;
381
+ return (...args) => {
382
+ window.clearTimeout(timeoutId);
383
+ timeoutId = window.setTimeout(() => {
384
+ callback(...args);
385
+ }, wait);
386
+ };
387
+ };
388
+
389
+ /**
390
+ * Components
391
+ */
392
+
393
+ /**
394
+ * Generated bundle index. Do not edit.
395
+ */
396
+
397
+ export { AuthService, ByuFooterComponent, ByuHeaderComponent, debounced, fetchSignal, readMaybeSignal };
398
+ //# sourceMappingURL=fhss-web-team-frontend-utils.mjs.map
@@ -0,0 +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/signals/debounced/debounced.ts","../../../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;\ntype JsonObject = { [key: string]: JsonValue };\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 { 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}","/**\n * Components\n */\nexport * from './lib/components/byu-footer/byu-footer.component'\nexport * from './lib/components/byu-header/byu-header.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":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Generated bundle index. Do not edit.
3
3
  */
4
- /// <amd-module name="components" />
4
+ /// <amd-module name="@fhss-web-team/frontend-utils" />
5
5
  export * from './public-api';
@@ -0,0 +1,27 @@
1
+ import { AuthService } from '../../services/auth/auth.service';
2
+ import * as i0 from "@angular/core";
3
+ type HeaderLink = {
4
+ text: string;
5
+ path: string;
6
+ };
7
+ type HeaderMenu = HeaderLink | {
8
+ text: string;
9
+ items: HeaderLink[];
10
+ };
11
+ export type HeaderConfig = {
12
+ title: HeaderLink;
13
+ subtitle?: HeaderLink;
14
+ breadcrumbs?: HeaderLink[];
15
+ menu?: HeaderMenu[];
16
+ };
17
+ export declare class ByuHeaderComponent {
18
+ auth: AuthService;
19
+ config: import("@angular/core").InputSignal<HeaderConfig | undefined>;
20
+ isHeaderLink(item: HeaderMenu): item is HeaderLink;
21
+ openDropdownText: string | null;
22
+ toggleDropdown(text: string): void;
23
+ isOpen(text: string): boolean;
24
+ static ɵfac: i0.ɵɵFactoryDeclaration<ByuHeaderComponent, never>;
25
+ static ɵcmp: i0.ɵɵComponentDeclaration<ByuHeaderComponent, "byu-header", never, { "config": { "alias": "config"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
26
+ }
27
+ export {};
@@ -0,0 +1,100 @@
1
+ import { KeycloakFlow, KeycloakLogoutOptions, KeycloakResourceAccess, KeycloakResponseMode, KeycloakResponseType, KeycloakRoles, KeycloakTokenParsed } from 'keycloak-js';
2
+ import * as i0 from "@angular/core";
3
+ export declare class AuthService {
4
+ private keycloak;
5
+ private keycloakSignal;
6
+ login: (nextRoute?: string) => void;
7
+ logout: (options?: KeycloakLogoutOptions) => Promise<void>;
8
+ /**
9
+ * A signal for the keycloak-js authenticated property
10
+ *
11
+ * Is `true` if the user is authenticated, `false` otherwise.
12
+ */
13
+ authenticated: import("@angular/core").Signal<boolean | undefined>;
14
+ /**
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.
18
+ */
19
+ token: import("@angular/core").Signal<string | undefined>;
20
+ /**
21
+ * A signal that will return the Bearer Token for use in the Authorization header
22
+ */
23
+ bearerToken: import("@angular/core").Signal<string | undefined>;
24
+ /**
25
+ * A signal for the keycloak-js tokenParsed property
26
+ *
27
+ * The parsed token as a JavaScript object.
28
+ */
29
+ tokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
30
+ /**
31
+ * A signal for the keycloak-js subject property
32
+ *
33
+ * The user id.
34
+ */
35
+ userId: import("@angular/core").Signal<string | undefined>;
36
+ /**
37
+ * A signal for the keycloak-js idToken property
38
+ *
39
+ * The base64 encoded ID token.
40
+ */
41
+ idToken: import("@angular/core").Signal<string | undefined>;
42
+ /**
43
+ * A signal for the keycloak-js idTokenParsed property
44
+ *
45
+ * The parsed id token as a JavaScript object.
46
+ */
47
+ idTokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
48
+ /**
49
+ * A signal for the keycloak-js realmAccess property
50
+ *
51
+ * The realm roles associated with the token.
52
+ */
53
+ realmAccess: import("@angular/core").Signal<KeycloakRoles | undefined>;
54
+ /**
55
+ * A signal for the keycloak-js resourceAccess property
56
+ *
57
+ * The resource roles associated with the token.
58
+ */
59
+ resourceAccess: import("@angular/core").Signal<KeycloakResourceAccess | undefined>;
60
+ /**
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.
64
+ */
65
+ refreshToken: import("@angular/core").Signal<string | undefined>;
66
+ /**
67
+ * A signal for the keycloak-js refreshTokenParsed property
68
+ *
69
+ * The parsed refresh token as a JavaScript object.
70
+ */
71
+ refreshTokenParsed: import("@angular/core").Signal<KeycloakTokenParsed | undefined>;
72
+ /**
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.
77
+ */
78
+ timeSkew: import("@angular/core").Signal<number | undefined>;
79
+ /**
80
+ * A signal for the keycloak-js responseMode property
81
+ *
82
+ * Response mode passed in init (default value is fragment).
83
+ */
84
+ responseMode: import("@angular/core").Signal<KeycloakResponseMode | undefined>;
85
+ /**
86
+ * A signal for the keycloak-js flow property
87
+ *
88
+ * Flow passed in init.
89
+ */
90
+ flow: import("@angular/core").Signal<KeycloakFlow | undefined>;
91
+ /**
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.
96
+ */
97
+ responseType: import("@angular/core").Signal<KeycloakResponseType | undefined>;
98
+ static ɵfac: i0.ɵɵFactoryDeclaration<AuthService, never>;
99
+ static ɵprov: i0.ɵɵInjectableDeclaration<AuthService>;
100
+ }
package/package.json CHANGED
@@ -1,39 +1,23 @@
1
1
  {
2
2
  "name": "@fhss-web-team/frontend-utils",
3
- "version": "0.0.2",
4
- "description": "Utility code for the frontend of FHSS Web applications.",
5
- "author": "FHSS Web Team",
6
- "type": "module",
3
+ "version": "0.0.4",
4
+ "peerDependencies": {
5
+ "@angular/common": "^19.2.0",
6
+ "@angular/core": "^19.2.0"
7
+ },
8
+ "dependencies": {
9
+ "tslib": "^2.3.0"
10
+ },
11
+ "sideEffects": false,
12
+ "module": "fesm2022/fhss-web-team-frontend-utils.mjs",
13
+ "typings": "index.d.ts",
7
14
  "exports": {
8
- "./components": {
9
- "import": "./dist/components/fesm2022/components.mjs",
10
- "types": "./dist/components/index.d.ts"
15
+ "./package.json": {
16
+ "default": "./package.json"
11
17
  },
12
- "./signals": {
13
- "import": "./dist/signals/index.js",
14
- "types": "./dist/signals/index.d.ts"
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "default": "./fesm2022/fhss-web-team-frontend-utils.mjs"
15
21
  }
16
- },
17
- "files": [
18
- "dist",
19
- "README.md"
20
- ],
21
- "bin": {
22
- "fhss-cli": "./dist/cli/cli.js"
23
- },
24
- "scripts": {
25
- "build:components": "ng-packagr -p projects/components/ng-package.json",
26
- "build:signals": "tsc -p tsconfig.signals.json",
27
- "build:cli": "tsc -p tsconfig.cli.json",
28
- "build": "npm run build:components && npm run build:signals && npm run build:cli",
29
- "push": "npm run build && npm publish --access public"
30
- },
31
- "devDependencies": {
32
- "@types/node": "^22.15.3",
33
- "ng-packagr": "^19.2.2",
34
- "typescript": "^5.8.3"
35
- },
36
- "dependencies": {
37
- "@angular/core": "^19.2.8"
38
22
  }
39
- }
23
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Components
3
+ */
4
+ export * from './lib/components/byu-footer/byu-footer.component';
5
+ export * from './lib/components/byu-header/byu-header.component';
6
+ /**
7
+ * Services
8
+ */
9
+ export * from './lib/services/auth/auth.service';
10
+ /**
11
+ * Signals
12
+ */
13
+ export * from './lib/signals/fetch-signal/fetch-signal';
14
+ export * from './lib/signals/debounced/debounced';
package/dist/cli/cli.d.ts DELETED
File without changes
package/dist/cli/cli.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- console.log("FHSS CLI works!");
@@ -1,19 +0,0 @@
1
- import * as i0 from '@angular/core';
2
- import { Component } from '@angular/core';
3
-
4
- class ByuFooterComponent {
5
- currentYear = new Date().getFullYear(); // Automatically updates the year
6
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.8", type: ByuFooterComponent, isStandalone: true, selector: "byu-footer", ngImport: i0, template: "<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | \u00A9 {{ 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", styles: ["footer{font-family:HCo Ringside Narrow SSm,Open Sans,Helvetica,Arial,sans-serif;font-weight:400;font-size:14px;color:#fff;background-color:#002e5d;text-align:center;padding:10px 20px}p{margin:8px 0}a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}.title{font-family:HCo Ringside Narrow SSm Bold,Open Sans,Helvetica,Arial,sans-serif;font-weight:700;font-size:20px;letter-spacing:5px;margin-bottom:18px}.title a:hover{text-decoration:none}\n"] });
8
- }
9
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.8", ngImport: i0, type: ByuFooterComponent, decorators: [{
10
- type: Component,
11
- args: [{ selector: 'byu-footer', standalone: true, template: "<footer>\n <p class=\"title\"><a href=\"https://www.byu.edu/\">BRIGHAM YOUNG UNIVERSITY</a></p>\n <p>Provo, UT 84602, USA | \u00A9 {{ 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", styles: ["footer{font-family:HCo Ringside Narrow SSm,Open Sans,Helvetica,Arial,sans-serif;font-weight:400;font-size:14px;color:#fff;background-color:#002e5d;text-align:center;padding:10px 20px}p{margin:8px 0}a{text-decoration:none;color:inherit}a:hover{text-decoration:underline}.title{font-family:HCo Ringside Narrow SSm Bold,Open Sans,Helvetica,Arial,sans-serif;font-weight:700;font-size:20px;letter-spacing:5px;margin-bottom:18px}.title a:hover{text-decoration:none}\n"] }]
12
- }] });
13
-
14
- /**
15
- * Generated bundle index. Do not edit.
16
- */
17
-
18
- export { ByuFooterComponent };
19
- //# sourceMappingURL=components.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"components.mjs","sources":["../../../projects/components/src/lib/byu-footer/byu-footer.component.ts","../../../projects/components/src/lib/byu-footer/byu-footer.component.html","../../../projects/components/src/components.ts"],"sourcesContent":["import { Component } from '@angular/core';\n\n@Component({\n selector: 'byu-footer',\n templateUrl: './byu-footer.component.html',\n styleUrl: './byu-footer.component.scss',\n standalone: true\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","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;MAQa,kBAAkB,CAAA;IAC7B,WAAW,GAAW,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;uGADpC,kBAAkB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAlB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,kBAAkB,sECR/B,2XAUA,EAAA,MAAA,EAAA,CAAA,+cAAA,CAAA,EAAA,CAAA;;2FDFa,kBAAkB,EAAA,UAAA,EAAA,CAAA;kBAN9B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,YAAY,cAGV,IAAI,EAAA,QAAA,EAAA,2XAAA,EAAA,MAAA,EAAA,CAAA,+cAAA,CAAA,EAAA;;;AENlB;;AAEG;;;;"}
@@ -1 +0,0 @@
1
- export * from './lib/byu-footer/byu-footer.component';
@@ -1,18 +0,0 @@
1
- import { effect, signal } from "@angular/core";
2
- export const debounced = (inputSignal, wait = 400) => {
3
- const debouncedSignal = signal(inputSignal());
4
- const setSignal = debounce((value) => debouncedSignal.set(value), wait);
5
- effect(() => {
6
- setSignal(inputSignal());
7
- });
8
- return debouncedSignal;
9
- };
10
- const debounce = (callback, wait) => {
11
- let timeoutId;
12
- return (...args) => {
13
- window.clearTimeout(timeoutId);
14
- timeoutId = window.setTimeout(() => {
15
- callback(...args);
16
- }, wait);
17
- };
18
- };
@@ -1,171 +0,0 @@
1
- import { computed, effect, signal } from '@angular/core';
2
- /**
3
- * Reads the underlying value from a MaybeSignal.
4
- *
5
- * This function is designed for use with reactive APIs (like fetchSignal) where it's important
6
- * that Angular's computed methods register the dependency. If the input is a Signal, invoking it
7
- * not only returns its current value but also tracks the signal for reactivity. If the input is
8
- * a plain value, it simply returns that value.
9
- *
10
- * @template T The type of the value.
11
- * @param value A plain value or a Signal that yields the value.
12
- * @returns The current value, with dependency tracking enabled if the input is a Signal.
13
- */
14
- export function readMaybeSignal(value) {
15
- return typeof value === 'function' ? value() : value;
16
- }
17
- /**
18
- * Creates a reactive fetch signal.
19
- *
20
- * The request function can include Signals for properties. These are unwrapped in the refresh function.
21
- */
22
- function createFetchSignal(request, method, transform, autoRefresh) {
23
- // Use a computed signal so that any changes to Signals in the request object trigger updates.
24
- const currentRequest = computed(request);
25
- const value = signal(undefined);
26
- const persistentValue = signal(undefined);
27
- const isLoading = signal(false);
28
- const error = signal(undefined);
29
- const statusCode = signal(undefined);
30
- const headers = signal(undefined);
31
- const refresh = async (abortSignal) => {
32
- // Reset signals for a fresh request.
33
- value.set(undefined);
34
- isLoading.set(true);
35
- error.set(undefined);
36
- statusCode.set(undefined);
37
- headers.set(undefined);
38
- // Unwrap the current request.
39
- const req = currentRequest();
40
- const url = req.url;
41
- const params = req.params;
42
- const requestHeaders = req.headers;
43
- const body = req.body;
44
- // Build URL with query parameters.
45
- let uri = url;
46
- if (params) {
47
- const searchParams = new URLSearchParams();
48
- for (const key in params) {
49
- if (params[key] !== undefined)
50
- searchParams.append(key, String(params[key]));
51
- }
52
- uri += (uri.includes('?') ? '&' : '?') + searchParams.toString();
53
- }
54
- try {
55
- const response = await fetch(uri, {
56
- method,
57
- headers: requestHeaders,
58
- // Only include a body if one is provided.
59
- body: body !== undefined ? JSON.stringify(body) : undefined,
60
- signal: abortSignal
61
- });
62
- statusCode.set(response.status);
63
- // Extract response headers.
64
- const headersObj = {};
65
- response.headers.forEach((val, key) => {
66
- headersObj[key] = val;
67
- });
68
- headers.set(headersObj);
69
- // if the response is ok, transform the body
70
- if (response.ok) {
71
- value.set(await transform(response));
72
- error.set(null);
73
- }
74
- else {
75
- // try to parse the error as json
76
- // set the error to null if this fails
77
- try {
78
- error.set(await response.json());
79
- }
80
- catch {
81
- error.set(null);
82
- }
83
- finally {
84
- value.set(null);
85
- }
86
- }
87
- }
88
- catch (err) {
89
- // if the request is aborted, ignore the error
90
- if (err.name !== 'AbortError') {
91
- console.error(err);
92
- }
93
- error.set(null);
94
- value.set(null);
95
- }
96
- finally {
97
- persistentValue.set(value());
98
- isLoading.set(false);
99
- }
100
- };
101
- if (autoRefresh) {
102
- effect((onCleanup) => {
103
- // read the current request to trigger re-run of this effect
104
- currentRequest();
105
- // pass abort signal to refresh on cleanup of effect
106
- const controller = new AbortController();
107
- onCleanup(() => controller.abort());
108
- // call refresh with this abort controller
109
- refresh(controller.signal);
110
- });
111
- }
112
- return { value, persistentValue, isLoading, error, statusCode, headers, refresh };
113
- }
114
- //
115
- // Helpers for attaching response transforms for GET/DELETE (which don’t include a body).
116
- //
117
- const createHelper = (method, transform) => (request, autoRefresh = false) => createFetchSignal(request, method, transform, autoRefresh);
118
- // Transforms
119
- const jsonTransformer = (response) => response.json();
120
- const textTransformer = (response) => response.text();
121
- const blobTransformer = (response) => response.blob();
122
- const arrayBufferTransformer = (response) => response.arrayBuffer();
123
- //
124
- // Build the defaults - GET chain
125
- //
126
- const fetchSignal = createHelper('GET', jsonTransformer);
127
- fetchSignal.json = createHelper('GET', jsonTransformer);
128
- fetchSignal.text = createHelper('GET', textTransformer);
129
- fetchSignal.blob = createHelper('GET', blobTransformer);
130
- fetchSignal.arrayBuffer = createHelper('GET', arrayBufferTransformer);
131
- //
132
- // Build the GET chain
133
- //
134
- fetchSignal.get = createHelper('GET', jsonTransformer);
135
- fetchSignal.get.json = createHelper('GET', jsonTransformer);
136
- fetchSignal.get.text = createHelper('GET', textTransformer);
137
- fetchSignal.get.blob = createHelper('GET', blobTransformer);
138
- fetchSignal.get.arrayBuffer = createHelper('GET', arrayBufferTransformer);
139
- //
140
- // Build the POST chain.
141
- //
142
- fetchSignal.post = createHelper('POST', jsonTransformer);
143
- fetchSignal.post.json = createHelper('POST', jsonTransformer);
144
- fetchSignal.post.text = createHelper('POST', textTransformer);
145
- fetchSignal.post.blob = createHelper('POST', blobTransformer);
146
- fetchSignal.post.arrayBuffer = createHelper('POST', arrayBufferTransformer);
147
- //
148
- // Build the PUT chain.
149
- //
150
- fetchSignal.put = createHelper('PUT', jsonTransformer);
151
- fetchSignal.put.json = createHelper('PUT', jsonTransformer);
152
- fetchSignal.put.text = createHelper('PUT', textTransformer);
153
- fetchSignal.put.blob = createHelper('PUT', blobTransformer);
154
- fetchSignal.put.arrayBuffer = createHelper('PUT', arrayBufferTransformer);
155
- //
156
- // Build the PATCH chain.
157
- //
158
- fetchSignal.patch = createHelper('PATCH', jsonTransformer);
159
- fetchSignal.patch.json = createHelper('PATCH', jsonTransformer);
160
- fetchSignal.patch.text = createHelper('PATCH', textTransformer);
161
- fetchSignal.patch.blob = createHelper('PATCH', blobTransformer);
162
- fetchSignal.patch.arrayBuffer = createHelper('PATCH', arrayBufferTransformer);
163
- //
164
- // Build the DELETE chain
165
- //
166
- fetchSignal.delete = createHelper('DELETE', jsonTransformer);
167
- fetchSignal.delete.json = createHelper('DELETE', jsonTransformer);
168
- fetchSignal.delete.text = createHelper('DELETE', textTransformer);
169
- fetchSignal.delete.blob = createHelper('DELETE', blobTransformer);
170
- fetchSignal.delete.arrayBuffer = createHelper('DELETE', arrayBufferTransformer);
171
- export { fetchSignal };
@@ -1,2 +0,0 @@
1
- export * from "./debounced/debounced";
2
- export * from "./fetch-signal/fetch-signal";
@@ -1,2 +0,0 @@
1
- export * from "./debounced/debounced";
2
- export * from "./fetch-signal/fetch-signal";
File without changes