@rolatech/angular-auth 20.3.0-beta.4 → 20.3.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,43 +1,27 @@
1
+ import { Location, isPlatformBrowser, CommonModule } from '@angular/common';
1
2
  import * as i0 from '@angular/core';
2
- import { Component, signal, computed, Injectable, input, inject, ChangeDetectionStrategy, output, model, PLATFORM_ID, InjectionToken, APP_INITIALIZER, makeEnvironmentProviders } from '@angular/core';
3
+ import { signal, computed, Injectable, inject, Component, input, ChangeDetectionStrategy, output, model, PLATFORM_ID, InjectionToken, APP_INITIALIZER, makeEnvironmentProviders } from '@angular/core';
4
+ import * as i1 from '@angular/material/button';
5
+ import { MatButtonModule } from '@angular/material/button';
6
+ import * as i2 from '@angular/material/progress-spinner';
7
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
8
+ import { ActivatedRoute, Router, RouterLink, provideRouter, ROUTES } from '@angular/router';
9
+ import { APP_CONFIG, AngularCommonModule } from '@rolatech/angular-common';
10
+ import { of, forkJoin, throwError, catchError as catchError$1, switchMap as switchMap$1, map as map$1, BehaviorSubject, NEVER, firstValueFrom } from 'rxjs';
11
+ import { HttpClient, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
12
+ import { switchMap, catchError, map, finalize, shareReplay, tap } from 'rxjs/operators';
3
13
  import { toSignal, toObservable } from '@angular/core/rxjs-interop';
4
- import * as i2 from '@angular/material/icon';
14
+ import * as i2$1 from '@angular/material/icon';
5
15
  import { MatIconModule } from '@angular/material/icon';
6
- import * as i1 from '@angular/material/menu';
16
+ import * as i1$1 from '@angular/material/menu';
7
17
  import { MatMenuModule } from '@angular/material/menu';
8
- import { RouterLink, Router, provideRouter, ROUTES } from '@angular/router';
9
18
  import { OnboardingApplicantService, SnackBarService } from '@rolatech/angular-services';
10
- import { switchMap, of, catchError, forkJoin, throwError, map as map$1, BehaviorSubject, NEVER, firstValueFrom } from 'rxjs';
11
19
  import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
12
- import { APP_CONFIG, AngularCommonModule } from '@rolatech/angular-common';
13
20
  import QRCode from 'qrcode';
14
- import { MatButtonModule } from '@angular/material/button';
15
- import { HttpClient, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
16
- import { switchMap as switchMap$1, catchError as catchError$1, map, finalize, shareReplay, tap, filter } from 'rxjs/operators';
17
21
  import { SpinnerComponent, AngularComponentsModule } from '@rolatech/angular-components';
18
- import * as i1$1 from '@angular/forms';
19
- import * as i2$1 from '@angular/material/form-field';
22
+ import * as i1$2 from '@angular/forms';
23
+ import * as i2$2 from '@angular/material/form-field';
20
24
  import * as i3 from '@angular/material/input';
21
- import { isPlatformBrowser, CommonModule } from '@angular/common';
22
- import { MatSnackBar } from '@angular/material/snack-bar';
23
-
24
- class ForbiddenComponent {
25
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ForbiddenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
26
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", type: ForbiddenComponent, isStandalone: true, selector: "rolatech-forbidden", ngImport: i0, template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>403.</b>\n <p>Forbidden page.</p>\n </div>\n</div>\n", styles: [""] });
27
- }
28
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ForbiddenComponent, decorators: [{
29
- type: Component,
30
- args: [{ selector: 'rolatech-forbidden', template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>403.</b>\n <p>Forbidden page.</p>\n </div>\n</div>\n" }]
31
- }] });
32
-
33
- class UnauthorizedComponent {
34
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: UnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
35
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", type: UnauthorizedComponent, isStandalone: true, selector: "rolatech-unauthorized", ngImport: i0, template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>401.</b>\n <p>Unauthorized page.</p>\n </div>\n</div>\n", styles: [""] });
36
- }
37
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: UnauthorizedComponent, decorators: [{
38
- type: Component,
39
- args: [{ selector: 'rolatech-unauthorized', template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>401.</b>\n <p>Unauthorized page.</p>\n </div>\n</div>\n" }]
40
- }] });
41
25
 
42
26
  class AuthContextStore {
43
27
  state = signal({
@@ -220,128 +204,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
220
204
  args: [{ providedIn: 'root' }]
221
205
  }] });
222
206
 
223
- const PLATFORM_ADMIN_ROLES = ['PLATFORM_ADMIN', 'ROLE_PLATFORM_ADMIN'];
224
- const APPLICATION_OWNER_ROLES = ['APPLICATION_OWNER', 'APP_OWNER', 'ROLE_APPLICATION_OWNER', 'ROLE_APP_OWNER'];
225
- const APPLICATION_ADMIN_ROLES = ['APPLICATION_ADMIN', 'APP_ADMIN', 'ROLE_APPLICATION_ADMIN', 'ROLE_APP_ADMIN'];
226
- const ORGANIZATION_OWNER_ROLES = ['ORGANIZATION_OWNER', 'ORG_OWNER', 'ROLE_ORGANIZATION_OWNER', 'ROLE_ORG_OWNER'];
227
- const ORGANIZATION_ADMIN_ROLES = ['ORGANIZATION_ADMIN', 'ORG_ADMIN', 'ROLE_ORGANIZATION_ADMIN', 'ROLE_ORG_ADMIN'];
228
- const ORGANIZATION_MEMBER_ROLES = ['ORGANIZATION_MEMBER', 'ORG_MEMBER', 'ROLE_ORGANIZATION_MEMBER', 'ROLE_ORG_MEMBER'];
229
- const ORGANIZATION_STAFF_ROLES = ['ORGANIZATION_STAFF', 'ORG_STAFF', 'ROLE_ORGANIZATION_STAFF', 'ROLE_ORG_STAFF'];
230
-
231
- class AgentAccessMenuComponent {
232
- applicationCode = input('primecasa', ...(ngDevMode ? [{ debugName: "applicationCode" }] : []));
233
- applyRouterLink = input('/agents', ...(ngDevMode ? [{ debugName: "applyRouterLink" }] : []));
234
- dashboardRouterLink = input('/organization', ...(ngDevMode ? [{ debugName: "dashboardRouterLink" }] : []));
235
- showApplyAction = input(true, ...(ngDevMode ? [{ debugName: "showApplyAction" }] : []));
236
- dashboardLabel = input('Agent dashboard', ...(ngDevMode ? [{ debugName: "dashboardLabel" }] : []));
237
- authStore = inject(AuthStore);
238
- authContextStore = inject(AuthContextStore);
239
- onboardingApplicantService = inject(OnboardingApplicantService);
240
- mine = toSignal(toObservable(computed(() => ({
241
- authenticated: this.authStore.authenticated(),
242
- loaded: this.authStore.loaded(),
243
- applicationCode: this.applicationCode(),
244
- }))).pipe(switchMap(({ authenticated, loaded, applicationCode }) => {
245
- if (!loaded || !authenticated || !applicationCode) {
246
- return of(null);
247
- }
248
- return this.onboardingApplicantService.listMine(applicationCode).pipe(catchError(() => of([])));
249
- })), { initialValue: null });
250
- latestApplication = computed(() => {
251
- const applications = this.mine();
252
- if (!applications?.length) {
253
- return null;
254
- }
255
- return [...applications].sort((left, right) => this.toTimestamp(right) - this.toTimestamp(left))[0] ?? null;
256
- }, ...(ngDevMode ? [{ debugName: "latestApplication" }] : []));
257
- hasDashboardAccess = computed(() => {
258
- this.authStore.loaded();
259
- const appId = this.authContextStore.appId() ?? this.authStore.primaryApplication()?.appId ?? this.authStore.primaryOrganization()?.appId ?? null;
260
- return this.authStore.hasOrganizationRole(appId, null, ...ORGANIZATION_OWNER_ROLES, ...ORGANIZATION_ADMIN_ROLES, ...ORGANIZATION_MEMBER_ROLES);
261
- }, ...(ngDevMode ? [{ debugName: "hasDashboardAccess" }] : []));
262
- entry = computed(() => {
263
- if (this.hasDashboardAccess()) {
264
- return {
265
- icon: 'storefront',
266
- label: this.dashboardLabel(),
267
- routerLink: this.dashboardRouterLink(),
268
- };
269
- }
270
- const application = this.latestApplication();
271
- if (application) {
272
- return {
273
- icon: 'assignment',
274
- label: this.applicationLabel(application.status),
275
- routerLink: this.applicationRouterLink(application),
276
- };
207
+ const LAST_ALLOWED_URL_KEY = 'rolatech.auth.last-allowed-url';
208
+ function normalizeUrl(url) {
209
+ if (!url) {
210
+ return null;
211
+ }
212
+ if (url.startsWith('http://') || url.startsWith('https://')) {
213
+ try {
214
+ const parsed = new URL(url);
215
+ return `${parsed.pathname}${parsed.search}${parsed.hash}` || '/';
277
216
  }
278
- if (!this.showApplyAction()) {
217
+ catch {
279
218
  return null;
280
219
  }
281
- return {
282
- icon: 'badge',
283
- label: 'Apply as agent',
284
- routerLink: this.applyRouterLink(),
285
- };
286
- }, ...(ngDevMode ? [{ debugName: "entry" }] : []));
287
- applicationLabel(status) {
288
- switch (status) {
289
- case 'APPROVED':
290
- return 'Agent application approved';
291
- case 'FAILED':
292
- return 'Agent application result';
293
- case 'NEED_MORE_INFO':
294
- return 'Agent application updates';
295
- case 'SUBMITTED':
296
- case 'IN_REVIEW':
297
- return 'Agent application review';
298
- default:
299
- return 'Continue agent application';
300
- }
301
- }
302
- applicationRouterLink(application) {
303
- const route = this.canOpenReview(application) ? 'review' : 'form';
304
- return ['/agents', application.id, route];
305
220
  }
306
- canOpenReview(application) {
307
- const progress = application.progress;
308
- if (progress && !this.isFormComplete(progress)) {
309
- return false;
310
- }
311
- return application.status !== 'DRAFT' || this.isFormComplete(progress);
221
+ return url.startsWith('/') ? url : `/${url}`;
222
+ }
223
+ function isAccessBoundaryUrl(url) {
224
+ return url.startsWith('/forbidden') || url.startsWith('/unauthorized');
225
+ }
226
+ function rememberAllowedUrl(url) {
227
+ if (typeof window === 'undefined') {
228
+ return;
312
229
  }
313
- isFormComplete(progress) {
314
- return Boolean(progress?.profileCompleted && progress.qualificationCompleted && progress.financialCompleted && progress.bankingCompleted);
230
+ const normalizedUrl = normalizeUrl(url);
231
+ if (!normalizedUrl || isAccessBoundaryUrl(normalizedUrl)) {
232
+ return;
315
233
  }
316
- toTimestamp(application) {
317
- return Date.parse(application.updatedAt ?? application.createdAt ?? '') || 0;
234
+ window.sessionStorage.setItem(LAST_ALLOWED_URL_KEY, normalizedUrl);
235
+ }
236
+ function readLastAllowedUrl() {
237
+ if (typeof window === 'undefined') {
238
+ return null;
318
239
  }
319
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentAccessMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
320
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AgentAccessMenuComponent, isStandalone: true, selector: "rolatech-agent-access-menu", inputs: { applicationCode: { classPropertyName: "applicationCode", publicName: "applicationCode", isSignal: true, isRequired: false, transformFunction: null }, applyRouterLink: { classPropertyName: "applyRouterLink", publicName: "applyRouterLink", isSignal: true, isRequired: false, transformFunction: null }, dashboardRouterLink: { classPropertyName: "dashboardRouterLink", publicName: "dashboardRouterLink", isSignal: true, isRequired: false, transformFunction: null }, showApplyAction: { classPropertyName: "showApplyAction", publicName: "showApplyAction", isSignal: true, isRequired: false, transformFunction: null }, dashboardLabel: { classPropertyName: "dashboardLabel", publicName: "dashboardLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
321
- @if (entry(); as item) {
322
- <a mat-menu-item [routerLink]="item.routerLink" class="px-6 flex items-center cursor-pointer">
323
- <mat-icon>{{ item.icon }}</mat-icon>
324
- <span class="flex items-center pl-1">{{ item.label }}</span>
325
- </a>
240
+ const value = normalizeUrl(window.sessionStorage.getItem(LAST_ALLOWED_URL_KEY));
241
+ if (!value || isAccessBoundaryUrl(value)) {
242
+ return null;
326
243
  }
327
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
244
+ return value;
328
245
  }
329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentAccessMenuComponent, decorators: [{
330
- type: Component,
331
- args: [{
332
- selector: 'rolatech-agent-access-menu',
333
- imports: [MatMenuModule, MatIconModule, RouterLink],
334
- template: `
335
- @if (entry(); as item) {
336
- <a mat-menu-item [routerLink]="item.routerLink" class="px-6 flex items-center cursor-pointer">
337
- <mat-icon>{{ item.icon }}</mat-icon>
338
- <span class="flex items-center pl-1">{{ item.label }}</span>
339
- </a>
246
+ function createForbiddenUrlTree(router, deniedUrl, reason = 'forbidden') {
247
+ const queryParams = {
248
+ reason,
249
+ };
250
+ const normalizedDeniedUrl = normalizeUrl(deniedUrl);
251
+ if (normalizedDeniedUrl) {
252
+ queryParams['denied'] = normalizedDeniedUrl;
340
253
  }
341
- `,
342
- changeDetection: ChangeDetectionStrategy.OnPush,
343
- }]
344
- }], propDecorators: { applicationCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "applicationCode", required: false }] }], applyRouterLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyRouterLink", required: false }] }], dashboardRouterLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardRouterLink", required: false }] }], showApplyAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApplyAction", required: false }] }], dashboardLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardLabel", required: false }] }] } });
254
+ const fallbackUrl = readLastAllowedUrl();
255
+ if (fallbackUrl) {
256
+ queryParams['fallback'] = fallbackUrl;
257
+ }
258
+ return router.createUrlTree(['/forbidden'], { queryParams });
259
+ }
345
260
 
346
261
  class AuthService {
347
262
  environment = inject(APP_CONFIG);
@@ -370,15 +285,15 @@ class AuthService {
370
285
  .get(`${this.environment.baseUrl}/auth/introspect`, {
371
286
  withCredentials: true,
372
287
  })
373
- .pipe(switchMap$1((res) => {
288
+ .pipe(switchMap((res) => {
374
289
  if (!res.authenticated) {
375
290
  this.authStore.clear();
376
291
  this.authContextStore.clear();
377
292
  return of(res);
378
293
  }
379
294
  return forkJoin({
380
- user: this.me().pipe(catchError$1(() => of(null))),
381
- context: this.getMeContext().pipe(catchError$1(() => of(this.emptyContext(res)))),
295
+ user: this.me().pipe(catchError(() => of(null))),
296
+ context: this.getMeContext().pipe(catchError(() => of(this.emptyContext(res)))),
382
297
  }).pipe(map(({ user, context }) => {
383
298
  this.authStore.update({
384
299
  authenticated: true,
@@ -395,7 +310,7 @@ class AuthService {
395
310
  this.syncContext(context);
396
311
  return res;
397
312
  }));
398
- }), catchError$1((error) => {
313
+ }), catchError((error) => {
399
314
  this.authStore.clear();
400
315
  this.authContextStore.clear();
401
316
  return throwError(() => error);
@@ -584,6 +499,214 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
584
499
  args: [{ providedIn: 'root' }]
585
500
  }] });
586
501
 
502
+ class ForbiddenComponent {
503
+ route = inject(ActivatedRoute);
504
+ router = inject(Router);
505
+ location = inject(Location);
506
+ authService = inject(AuthService);
507
+ authStore = inject(AuthStore);
508
+ authContextStore = inject(AuthContextStore);
509
+ environment = inject(APP_CONFIG);
510
+ switchingAccount = signal(false, ...(ngDevMode ? [{ debugName: "switchingAccount" }] : []));
511
+ deniedUrl = this.normalizeRoute(this.route.snapshot.queryParamMap.get('denied'));
512
+ fallbackUrl = this.resolveFallbackUrl();
513
+ authenticated = this.authStore.authenticated;
514
+ accountLabel = computed(() => this.authStore.username() ?? this.authStore.user()?.email ?? 'this account', ...(ngDevMode ? [{ debugName: "accountLabel" }] : []));
515
+ goBackOrFallback() {
516
+ if (this.fallbackUrl) {
517
+ this.router.navigateByUrl(this.fallbackUrl);
518
+ return;
519
+ }
520
+ this.location.back();
521
+ }
522
+ signOutAndSwitchAccount() {
523
+ const signInUrl = this.buildSignInUrl();
524
+ this.switchingAccount.set(true);
525
+ this.authService
526
+ .logout()
527
+ .pipe(catchError$1(() => {
528
+ this.authStore.clear();
529
+ this.authContextStore.clear();
530
+ return of(null);
531
+ }))
532
+ .subscribe({
533
+ next: () => {
534
+ window.location.href = signInUrl;
535
+ },
536
+ error: () => {
537
+ window.location.href = signInUrl;
538
+ },
539
+ });
540
+ }
541
+ goToSignIn() {
542
+ window.location.href = this.buildSignInUrl();
543
+ }
544
+ buildSignInUrl() {
545
+ const continueUrl = this.buildContinueUrl();
546
+ return `${this.environment.accountsUrl}/signin?continue=${encodeURIComponent(continueUrl)}`;
547
+ }
548
+ buildContinueUrl() {
549
+ if (typeof window === 'undefined') {
550
+ return this.environment.myaccountUrl;
551
+ }
552
+ const target = this.deniedUrl || this.fallbackUrl || '/';
553
+ return new URL(target, window.location.origin).toString();
554
+ }
555
+ resolveFallbackUrl() {
556
+ const queryFallback = this.normalizeRoute(this.route.snapshot.queryParamMap.get('fallback'));
557
+ const storedFallback = this.normalizeRoute(readLastAllowedUrl());
558
+ const fallback = queryFallback || storedFallback;
559
+ if (!fallback || fallback === this.deniedUrl || fallback.startsWith('/forbidden')) {
560
+ return null;
561
+ }
562
+ return fallback;
563
+ }
564
+ normalizeRoute(url) {
565
+ if (!url) {
566
+ return null;
567
+ }
568
+ return url.startsWith('/') ? url : `/${url}`;
569
+ }
570
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ForbiddenComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
571
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: ForbiddenComponent, isStandalone: true, selector: "rolatech-forbidden", ngImport: i0, template: "<section class=\"forbidden-page\">\n <article class=\"forbidden-page__card\">\n <span class=\"forbidden-page__eyebrow\">403</span>\n <h1 class=\"forbidden-page__title\">This account cannot open that page</h1>\n\n @if (authenticated()) {\n <p class=\"forbidden-page__description\">\n You are signed in as <strong>{{ accountLabel() }}</strong>, but that account does not have permission to view this route.\n </p>\n } @else {\n <p class=\"forbidden-page__description\">You do not have permission to open this route with the current session.</p>\n }\n\n @if (deniedUrl) {\n <div class=\"forbidden-page__route\">\n <span class=\"forbidden-page__route-label\">Attempted page</span>\n <code class=\"forbidden-page__route-value\">{{ deniedUrl }}</code>\n </div>\n }\n\n <p class=\"forbidden-page__hint\">\n Return to a page that still works for this account, or sign out and continue with the correct account to retry access.\n </p>\n\n <div class=\"forbidden-page__actions\">\n <button mat-stroked-button type=\"button\" (click)=\"goBackOrFallback()\">\n {{ fallbackUrl ? 'Back to allowed page' : 'Go back' }}\n </button>\n\n @if (authenticated()) {\n <button mat-flat-button type=\"button\" (click)=\"signOutAndSwitchAccount()\" [disabled]=\"switchingAccount()\">\n @if (switchingAccount()) {\n <mat-progress-spinner diameter=\"18\" mode=\"indeterminate\"></mat-progress-spinner>\n }\n <span>{{ switchingAccount() ? 'Switching account...' : 'Sign out and switch account' }}</span>\n </button>\n } @else {\n <button mat-flat-button type=\"button\" (click)=\"goToSignIn()\">Sign in</button>\n }\n </div>\n\n <p class=\"forbidden-page__support\">\n If this should be available for the current account, ask an administrator to review the role or permission assignment.\n </p>\n </article>\n</section>\n", styles: [":host{display:grid;min-height:100dvh;padding:1.5rem;place-items:center;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 42%),var(--rt-base-background, #ffffff)}.forbidden-page{width:min(100%,42rem)}.forbidden-page__card{display:flex;flex-direction:column;gap:1rem;padding:1.5rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 24px 56px -42px color-mix(in srgb,var(--rt-text-primary) 24%,transparent)}.forbidden-page__eyebrow{display:inline-flex;align-self:flex-start;padding:.32rem .72rem;border-radius:9999px;background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 12%,transparent);color:var(--mat-sys-error, #b91c1c);font-size:.78rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.forbidden-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,4vw,2.4rem);line-height:1.05}.forbidden-page__description,.forbidden-page__hint,.forbidden-page__support{margin:0;color:var(--rt-text-secondary);line-height:1.6}.forbidden-page__route{display:flex;flex-direction:column;gap:.45rem;padding:.95rem 1rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1rem;background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent)}.forbidden-page__route-label{font-size:.8rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--rt-text-secondary)}.forbidden-page__route-value{overflow-wrap:anywhere;color:var(--rt-text-primary);font-family:SFMono-Regular,ui-monospace,monospace;font-size:.95rem}.forbidden-page__actions{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center}.forbidden-page__actions button{display:inline-flex;gap:.55rem;align-items:center}@media(max-width:640px){:host{padding:1rem}.forbidden-page__card{padding:1.2rem}.forbidden-page__actions{flex-direction:column;align-items:stretch}.forbidden-page__actions button{justify-content:center}}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }] });
572
+ }
573
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: ForbiddenComponent, decorators: [{
574
+ type: Component,
575
+ args: [{ selector: 'rolatech-forbidden', imports: [MatButtonModule, MatProgressSpinnerModule], template: "<section class=\"forbidden-page\">\n <article class=\"forbidden-page__card\">\n <span class=\"forbidden-page__eyebrow\">403</span>\n <h1 class=\"forbidden-page__title\">This account cannot open that page</h1>\n\n @if (authenticated()) {\n <p class=\"forbidden-page__description\">\n You are signed in as <strong>{{ accountLabel() }}</strong>, but that account does not have permission to view this route.\n </p>\n } @else {\n <p class=\"forbidden-page__description\">You do not have permission to open this route with the current session.</p>\n }\n\n @if (deniedUrl) {\n <div class=\"forbidden-page__route\">\n <span class=\"forbidden-page__route-label\">Attempted page</span>\n <code class=\"forbidden-page__route-value\">{{ deniedUrl }}</code>\n </div>\n }\n\n <p class=\"forbidden-page__hint\">\n Return to a page that still works for this account, or sign out and continue with the correct account to retry access.\n </p>\n\n <div class=\"forbidden-page__actions\">\n <button mat-stroked-button type=\"button\" (click)=\"goBackOrFallback()\">\n {{ fallbackUrl ? 'Back to allowed page' : 'Go back' }}\n </button>\n\n @if (authenticated()) {\n <button mat-flat-button type=\"button\" (click)=\"signOutAndSwitchAccount()\" [disabled]=\"switchingAccount()\">\n @if (switchingAccount()) {\n <mat-progress-spinner diameter=\"18\" mode=\"indeterminate\"></mat-progress-spinner>\n }\n <span>{{ switchingAccount() ? 'Switching account...' : 'Sign out and switch account' }}</span>\n </button>\n } @else {\n <button mat-flat-button type=\"button\" (click)=\"goToSignIn()\">Sign in</button>\n }\n </div>\n\n <p class=\"forbidden-page__support\">\n If this should be available for the current account, ask an administrator to review the role or permission assignment.\n </p>\n </article>\n</section>\n", styles: [":host{display:grid;min-height:100dvh;padding:1.5rem;place-items:center;background:radial-gradient(circle at top left,color-mix(in srgb,var(--rt-brand-color) 14%,transparent),transparent 42%),var(--rt-base-background, #ffffff)}.forbidden-page{width:min(100%,42rem)}.forbidden-page__card{display:flex;flex-direction:column;gap:1rem;padding:1.5rem;border:1px solid var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1.5rem;background:color-mix(in srgb,var(--rt-raised-background, #ffffff) 96%,transparent);box-shadow:0 24px 56px -42px color-mix(in srgb,var(--rt-text-primary) 24%,transparent)}.forbidden-page__eyebrow{display:inline-flex;align-self:flex-start;padding:.32rem .72rem;border-radius:9999px;background:color-mix(in srgb,var(--mat-sys-error, #b91c1c) 12%,transparent);color:var(--mat-sys-error, #b91c1c);font-size:.78rem;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.forbidden-page__title{margin:0;color:var(--rt-text-primary);font-size:clamp(1.8rem,4vw,2.4rem);line-height:1.05}.forbidden-page__description,.forbidden-page__hint,.forbidden-page__support{margin:0;color:var(--rt-text-secondary);line-height:1.6}.forbidden-page__route{display:flex;flex-direction:column;gap:.45rem;padding:.95rem 1rem;border:1px dashed var(--rt-border-color, rgba(15, 23, 42, .08));border-radius:1rem;background:color-mix(in srgb,var(--rt-10-percent-layer, rgba(15, 23, 42, .08)) 72%,transparent)}.forbidden-page__route-label{font-size:.8rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--rt-text-secondary)}.forbidden-page__route-value{overflow-wrap:anywhere;color:var(--rt-text-primary);font-family:SFMono-Regular,ui-monospace,monospace;font-size:.95rem}.forbidden-page__actions{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center}.forbidden-page__actions button{display:inline-flex;gap:.55rem;align-items:center}@media(max-width:640px){:host{padding:1rem}.forbidden-page__card{padding:1.2rem}.forbidden-page__actions{flex-direction:column;align-items:stretch}.forbidden-page__actions button{justify-content:center}}\n"] }]
576
+ }] });
577
+
578
+ class UnauthorizedComponent {
579
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: UnauthorizedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
580
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", type: UnauthorizedComponent, isStandalone: true, selector: "rolatech-unauthorized", ngImport: i0, template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>401.</b>\n <p>Unauthorized page.</p>\n </div>\n</div>\n", styles: [""] });
581
+ }
582
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: UnauthorizedComponent, decorators: [{
583
+ type: Component,
584
+ args: [{ selector: 'rolatech-unauthorized', template: "<div class=\"p-6 max-w-lg h-auto max-h-32\">\n <div class=\"flex flex-col\">\n <b>401.</b>\n <p>Unauthorized page.</p>\n </div>\n</div>\n" }]
585
+ }] });
586
+
587
+ const PLATFORM_ADMIN_ROLES = ['PLATFORM_ADMIN', 'ROLE_PLATFORM_ADMIN'];
588
+ const APPLICATION_OWNER_ROLES = ['APPLICATION_OWNER', 'APP_OWNER', 'ROLE_APPLICATION_OWNER', 'ROLE_APP_OWNER'];
589
+ const APPLICATION_ADMIN_ROLES = ['APPLICATION_ADMIN', 'APP_ADMIN', 'ROLE_APPLICATION_ADMIN', 'ROLE_APP_ADMIN'];
590
+ const ORGANIZATION_OWNER_ROLES = ['ORGANIZATION_OWNER', 'ORG_OWNER', 'ROLE_ORGANIZATION_OWNER', 'ROLE_ORG_OWNER'];
591
+ const ORGANIZATION_ADMIN_ROLES = ['ORGANIZATION_ADMIN', 'ORG_ADMIN', 'ROLE_ORGANIZATION_ADMIN', 'ROLE_ORG_ADMIN'];
592
+ const ORGANIZATION_MEMBER_ROLES = ['ORGANIZATION_MEMBER', 'ORG_MEMBER', 'ROLE_ORGANIZATION_MEMBER', 'ROLE_ORG_MEMBER'];
593
+ const ORGANIZATION_STAFF_ROLES = ['ORGANIZATION_STAFF', 'ORG_STAFF', 'ROLE_ORGANIZATION_STAFF', 'ROLE_ORG_STAFF'];
594
+
595
+ class AgentAccessMenuComponent {
596
+ applicationCode = input('primecasa', ...(ngDevMode ? [{ debugName: "applicationCode" }] : []));
597
+ applyRouterLink = input('/agents', ...(ngDevMode ? [{ debugName: "applyRouterLink" }] : []));
598
+ dashboardRouterLink = input('/organization', ...(ngDevMode ? [{ debugName: "dashboardRouterLink" }] : []));
599
+ showApplyAction = input(true, ...(ngDevMode ? [{ debugName: "showApplyAction" }] : []));
600
+ dashboardLabel = input('Agent dashboard', ...(ngDevMode ? [{ debugName: "dashboardLabel" }] : []));
601
+ authStore = inject(AuthStore);
602
+ authContextStore = inject(AuthContextStore);
603
+ onboardingApplicantService = inject(OnboardingApplicantService);
604
+ mine = toSignal(toObservable(computed(() => ({
605
+ authenticated: this.authStore.authenticated(),
606
+ loaded: this.authStore.loaded(),
607
+ applicationCode: this.applicationCode(),
608
+ }))).pipe(switchMap$1(({ authenticated, loaded, applicationCode }) => {
609
+ if (!loaded || !authenticated || !applicationCode) {
610
+ return of(null);
611
+ }
612
+ return this.onboardingApplicantService.listMine(applicationCode).pipe(catchError$1(() => of([])));
613
+ })), { initialValue: null });
614
+ latestApplication = computed(() => {
615
+ const applications = this.mine();
616
+ if (!applications?.length) {
617
+ return null;
618
+ }
619
+ return [...applications].sort((left, right) => this.toTimestamp(right) - this.toTimestamp(left))[0] ?? null;
620
+ }, ...(ngDevMode ? [{ debugName: "latestApplication" }] : []));
621
+ hasDashboardAccess = computed(() => {
622
+ this.authStore.loaded();
623
+ const appId = this.authContextStore.appId() ?? this.authStore.primaryApplication()?.appId ?? this.authStore.primaryOrganization()?.appId ?? null;
624
+ return this.authStore.hasOrganizationRole(appId, null, ...ORGANIZATION_OWNER_ROLES, ...ORGANIZATION_ADMIN_ROLES, ...ORGANIZATION_MEMBER_ROLES);
625
+ }, ...(ngDevMode ? [{ debugName: "hasDashboardAccess" }] : []));
626
+ entry = computed(() => {
627
+ if (this.hasDashboardAccess()) {
628
+ return {
629
+ icon: 'storefront',
630
+ label: this.dashboardLabel(),
631
+ routerLink: this.dashboardRouterLink(),
632
+ };
633
+ }
634
+ const application = this.latestApplication();
635
+ if (application) {
636
+ return {
637
+ icon: 'assignment',
638
+ label: this.applicationLabel(application.status),
639
+ routerLink: this.applicationRouterLink(application),
640
+ };
641
+ }
642
+ if (!this.showApplyAction()) {
643
+ return null;
644
+ }
645
+ return {
646
+ icon: 'badge',
647
+ label: 'Apply as agent',
648
+ routerLink: this.applyRouterLink(),
649
+ };
650
+ }, ...(ngDevMode ? [{ debugName: "entry" }] : []));
651
+ applicationLabel(status) {
652
+ switch (status) {
653
+ case 'APPROVED':
654
+ return 'Agent application approved';
655
+ case 'FAILED':
656
+ return 'Agent application result';
657
+ case 'NEED_MORE_INFO':
658
+ return 'Agent application updates';
659
+ case 'SUBMITTED':
660
+ case 'IN_REVIEW':
661
+ return 'Agent application review';
662
+ default:
663
+ return 'Continue agent application';
664
+ }
665
+ }
666
+ applicationRouterLink(application) {
667
+ const route = this.canOpenReview(application) ? 'review' : 'form';
668
+ return ['/agents', application.id, route];
669
+ }
670
+ canOpenReview(application) {
671
+ const progress = application.progress;
672
+ if (progress && !this.isFormComplete(progress)) {
673
+ return false;
674
+ }
675
+ return application.status !== 'DRAFT' || this.isFormComplete(progress);
676
+ }
677
+ isFormComplete(progress) {
678
+ return Boolean(progress?.profileCompleted && progress.qualificationCompleted && progress.financialCompleted && progress.bankingCompleted);
679
+ }
680
+ toTimestamp(application) {
681
+ return Date.parse(application.updatedAt ?? application.createdAt ?? '') || 0;
682
+ }
683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentAccessMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
684
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AgentAccessMenuComponent, isStandalone: true, selector: "rolatech-agent-access-menu", inputs: { applicationCode: { classPropertyName: "applicationCode", publicName: "applicationCode", isSignal: true, isRequired: false, transformFunction: null }, applyRouterLink: { classPropertyName: "applyRouterLink", publicName: "applyRouterLink", isSignal: true, isRequired: false, transformFunction: null }, dashboardRouterLink: { classPropertyName: "dashboardRouterLink", publicName: "dashboardRouterLink", isSignal: true, isRequired: false, transformFunction: null }, showApplyAction: { classPropertyName: "showApplyAction", publicName: "showApplyAction", isSignal: true, isRequired: false, transformFunction: null }, dashboardLabel: { classPropertyName: "dashboardLabel", publicName: "dashboardLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
685
+ @if (entry(); as item) {
686
+ <a mat-menu-item [routerLink]="item.routerLink" class="px-6 flex items-center cursor-pointer">
687
+ <mat-icon>{{ item.icon }}</mat-icon>
688
+ <span class="flex items-center pl-1">{{ item.label }}</span>
689
+ </a>
690
+ }
691
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i1$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
692
+ }
693
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AgentAccessMenuComponent, decorators: [{
694
+ type: Component,
695
+ args: [{
696
+ selector: 'rolatech-agent-access-menu',
697
+ imports: [MatMenuModule, MatIconModule, RouterLink],
698
+ template: `
699
+ @if (entry(); as item) {
700
+ <a mat-menu-item [routerLink]="item.routerLink" class="px-6 flex items-center cursor-pointer">
701
+ <mat-icon>{{ item.icon }}</mat-icon>
702
+ <span class="flex items-center pl-1">{{ item.label }}</span>
703
+ </a>
704
+ }
705
+ `,
706
+ changeDetection: ChangeDetectionStrategy.OnPush,
707
+ }]
708
+ }], propDecorators: { applicationCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "applicationCode", required: false }] }], applyRouterLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "applyRouterLink", required: false }] }], dashboardRouterLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardRouterLink", required: false }] }], showApplyAction: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApplyAction", required: false }] }], dashboardLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardLabel", required: false }] }] } });
709
+
587
710
  class FaceidDetectDialogComponent {
588
711
  dialogRef = inject(MatDialogRef);
589
712
  environment = inject(APP_CONFIG);
@@ -671,7 +794,7 @@ class AddressComponent {
671
794
  this.output.emit(this.address());
672
795
  }
673
796
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AddressComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
674
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AddressComponent, isStandalone: true, selector: "rolatech-address", inputs: { address: { classPropertyName: "address", publicName: "address", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { address: "addressChange", output: "output" }, ngImport: i0, template: "<div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Name </mat-label>\n <input matInput [(ngModel)]=\"address().name\" />\n </mat-form-field>\n @if (address().type === addressType.BILLING) {\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Email </mat-label>\n <input matInput [(ngModel)]=\"address().email\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Phone </mat-label>\n <input matInput [(ngModel)]=\"address().phone\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> province </mat-label>\n <input matInput [(ngModel)]=\"address().province\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> City </mat-label>\n <input matInput [(ngModel)]=\"address().city\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> District </mat-label>\n <input matInput [(ngModel)]=\"address().district\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street </mat-label>\n <input matInput [(ngModel)]=\"address().street\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Detail </mat-label>\n <input matInput [(ngModel)]=\"address().detail\" />\n </mat-form-field>\n</div>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: AngularCommonModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i2$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
797
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: AddressComponent, isStandalone: true, selector: "rolatech-address", inputs: { address: { classPropertyName: "address", publicName: "address", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { address: "addressChange", output: "output" }, ngImport: i0, template: "<div>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Name </mat-label>\n <input matInput [(ngModel)]=\"address().name\" />\n </mat-form-field>\n @if (address().type === addressType.BILLING) {\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Email </mat-label>\n <input matInput [(ngModel)]=\"address().email\" />\n </mat-form-field>\n }\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Phone </mat-label>\n <input matInput [(ngModel)]=\"address().phone\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> province </mat-label>\n <input matInput [(ngModel)]=\"address().province\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> City </mat-label>\n <input matInput [(ngModel)]=\"address().city\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> District </mat-label>\n <input matInput [(ngModel)]=\"address().district\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Street </mat-label>\n <input matInput [(ngModel)]=\"address().street\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\">\n <mat-label i18n> Detail </mat-label>\n <input matInput [(ngModel)]=\"address().detail\" />\n </mat-form-field>\n</div>\n", styles: ["mat-form-field{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: AngularCommonModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: AngularComponentsModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }] });
675
798
  }
676
799
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: AddressComponent, decorators: [{
677
800
  type: Component,
@@ -693,8 +816,7 @@ const AuthGuard = (route, state) => {
693
816
  const routeRoles = route.data['roles'];
694
817
  const passRoled = routeRoles ? roles?.some((r) => routeRoles.indexOf(r) >= 0) : true;
695
818
  if (authenticated && !passRoled) {
696
- router.navigate(['/forbidden']);
697
- return false;
819
+ return createForbiddenUrlTree(router, state.url, 'role');
698
820
  }
699
821
  if (authenticated) {
700
822
  if (isSignInPage) {
@@ -712,6 +834,7 @@ const AuthGuard = (route, state) => {
712
834
  return false;
713
835
  }
714
836
  // 3. Otherwise, if the user is already where they need to be, allow navigation
837
+ rememberAllowedUrl(state.url);
715
838
  return true;
716
839
  }
717
840
  else {
@@ -799,21 +922,34 @@ function matchesRouteAccessPolicy(authStore, authContextStore, policy) {
799
922
  }
800
923
 
801
924
  function accessGuard(policy, fallbackUrl = '/forbidden') {
802
- return () => {
925
+ return (_route, state) => {
803
926
  const router = inject(Router);
804
927
  const authService = inject(AuthService);
805
928
  const authStore = inject(AuthStore);
806
929
  const authContextStore = inject(AuthContextStore);
807
- return authService.ensureLoaded().pipe(map(() => (matchesRouteAccessPolicy(authStore, authContextStore, policy) ? true : router.parseUrl(fallbackUrl))));
930
+ return authService.ensureLoaded().pipe(map(() => {
931
+ if (matchesRouteAccessPolicy(authStore, authContextStore, policy)) {
932
+ rememberAllowedUrl(state.url);
933
+ return true;
934
+ }
935
+ return fallbackUrl === '/forbidden' ? createForbiddenUrlTree(router, state.url, 'policy') : router.parseUrl(fallbackUrl);
936
+ }));
808
937
  };
809
938
  }
810
939
  function accessMatchGuard(policy, fallbackUrl = '/forbidden') {
811
- return () => {
940
+ return (_route, segments) => {
812
941
  const router = inject(Router);
813
942
  const authService = inject(AuthService);
814
943
  const authStore = inject(AuthStore);
815
944
  const authContextStore = inject(AuthContextStore);
816
- return authService.ensureLoaded().pipe(map(() => (matchesRouteAccessPolicy(authStore, authContextStore, policy) ? true : router.parseUrl(fallbackUrl))));
945
+ const attemptedUrl = segments.length ? `/${segments.map((segment) => segment.path).join('/')}` : router.url;
946
+ return authService.ensureLoaded().pipe(map(() => {
947
+ if (matchesRouteAccessPolicy(authStore, authContextStore, policy)) {
948
+ rememberAllowedUrl(router.url);
949
+ return true;
950
+ }
951
+ return fallbackUrl === '/forbidden' ? createForbiddenUrlTree(router, attemptedUrl, 'policy') : router.parseUrl(fallbackUrl);
952
+ }));
817
953
  };
818
954
  }
819
955
 
@@ -848,25 +984,19 @@ function landingRedirectGuard(targets = {}) {
848
984
 
849
985
  const RoleGuard = (route, state) => {
850
986
  const authService = inject(AuthService);
851
- const environment = inject(APP_CONFIG);
852
987
  const platformId = inject(PLATFORM_ID);
853
- const snackBar = inject(MatSnackBar);
854
988
  const router = inject(Router);
855
- const isContinue = state.url.includes('continue');
856
- const continueUrl = route.queryParams['continue'];
857
989
  if (!isPlatformBrowser(platformId)) {
858
990
  return of(false);
859
991
  }
860
992
  const routeRoles = route.data['roles'];
861
- // if (!routeRoles) {
862
- // return of(false);
863
- // }
864
- return authService.ensureLoaded().pipe(filter((res) => res.roles !== null && res.roles.length > 0), map$1(({ roles }) => {
865
- const authed = roles.some((r) => routeRoles?.indexOf(r) >= 0);
866
- if (!authed) {
867
- router.navigate(['/forbidden']);
993
+ return authService.ensureLoaded().pipe(map$1(({ roles }) => {
994
+ const authed = routeRoles ? (roles ?? []).some((role) => routeRoles.indexOf(role) >= 0) : true;
995
+ if (authed) {
996
+ rememberAllowedUrl(state.url);
997
+ return true;
868
998
  }
869
- return authed;
999
+ return createForbiddenUrlTree(router, state.url, 'role');
870
1000
  }));
871
1001
  };
872
1002
 
@@ -1150,7 +1280,7 @@ class ErrorInterceptor {
1150
1280
  isRefreshingToken = false;
1151
1281
  authService = inject(AuthService);
1152
1282
  intercept(request, next) {
1153
- return next.handle(request).pipe(catchError((error) => {
1283
+ return next.handle(request).pipe(catchError$1((error) => {
1154
1284
  if (error instanceof HttpErrorResponse) {
1155
1285
  switch (error.status) {
1156
1286
  case 400:
@@ -1216,7 +1346,7 @@ class AuthInterceptor {
1216
1346
  // // window.location.href = res.headers.get('Location') as string;
1217
1347
  // }
1218
1348
  return res;
1219
- }), catchError$1((error) => {
1349
+ }), catchError((error) => {
1220
1350
  if (isPlatformBrowser(this.platformId)) {
1221
1351
  if (error.url?.includes('auth/introspect')) {
1222
1352
  // if (window.location.origin !== `${this.environment.accountsUrl}`) {
@@ -1284,7 +1414,7 @@ function provideAngularAuth(config) {
1284
1414
  if (config?.autoLoadSession === false || !isPlatformBrowser(platformId)) {
1285
1415
  return Promise.resolve();
1286
1416
  }
1287
- return firstValueFrom(authService.ensureLoaded().pipe(catchError$1(() => of(null))));
1417
+ return firstValueFrom(authService.ensureLoaded().pipe(catchError(() => of(null))));
1288
1418
  };
1289
1419
  },
1290
1420
  },