diff --git a/apps/client-asset-sg/src/app/app.component.html b/apps/client-asset-sg/src/app/app.component.html
index 31020944..4be1cdff 100644
--- a/apps/client-asset-sg/src/app/app.component.html
+++ b/apps/client-asset-sg/src/app/app.component.html
@@ -1,13 +1,26 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+ {{error}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/client-asset-sg/src/app/app.component.scss b/apps/client-asset-sg/src/app/app.component.scss
index 0da765b2..8b4f2f75 100644
--- a/apps/client-asset-sg/src/app/app.component.scss
+++ b/apps/client-asset-sg/src/app/app.component.scss
@@ -1,4 +1,6 @@
@use 'styles/variables';
+@use '@angular/material' as mat;
+@use '../theme' as *;
$app-bar-height: 88px;
$menu-bar-width: 80px;
@@ -44,6 +46,20 @@ asset-sg-menu-bar {
}
}
+.error {
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ grid-column: 1 / -1;
+ padding: 40px;
+ align-items: center;
+ font-size: 24px;
+ font-weight: bolder;
+
+ color: mat.get-contrast-color-from-palette($asset-sg-warn, 500);
+ background-color: mat.get-color-from-palette($asset-sg-warn, 600);
+}
+
.alerts {
position: fixed;
width: min(30rem, 95vw);
diff --git a/apps/client-asset-sg/src/app/app.component.ts b/apps/client-asset-sg/src/app/app.component.ts
index f6cd7e3a..5c26f564 100644
--- a/apps/client-asset-sg/src/app/app.component.ts
+++ b/apps/client-asset-sg/src/app/app.component.ts
@@ -8,6 +8,7 @@ import { debounceTime, fromEvent, map, startWith } from 'rxjs';
import { assert } from 'tsafe';
import { AuthService } from '@asset-sg/auth';
+import { ErrorService } from '@asset-sg/auth';
import { AppPortalService, appSharedStateActions, setCssCustomProperties } from '@asset-sg/client-shared';
import { AppState } from './state/app-state';
@@ -25,7 +26,10 @@ export class AppComponent {
private _httpClient = inject(HttpClient);
public appPortalService = inject(AppPortalService);
private store = inject(Store);
- public readonly router: Router = inject(Router);
+
+ readonly router: Router = inject(Router);
+ readonly errorService = inject(ErrorService);
+ readonly authService = inject(AuthService);
constructor(private readonly _authService: AuthService) {
this._httpClient
diff --git a/apps/client-asset-sg/src/app/app.module.ts b/apps/client-asset-sg/src/app/app.module.ts
index adce9024..928aa613 100644
--- a/apps/client-asset-sg/src/app/app.module.ts
+++ b/apps/client-asset-sg/src/app/app.module.ts
@@ -18,6 +18,7 @@ import { LetModule } from '@rx-angular/template/let';
import { PushModule } from '@rx-angular/template/push';
import { AuthInterceptor, AuthModule } from '@asset-sg/auth';
+import { ErrorService } from '@asset-sg/auth';
import {
AnchorComponent,
ButtonComponent,
@@ -114,6 +115,7 @@ registerLocaleData(locale_deCH, 'de-CH');
],
providers: [
provideSvgIcons(icons),
+ ErrorService,
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'fill', floatLabel: 'auto' } },
{ provide: CURRENT_LANG, useFactory: currentLangFactory },
diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts
index 2f757bd9..048d502d 100644
--- a/libs/auth/src/index.ts
+++ b/libs/auth/src/index.ts
@@ -1,3 +1,4 @@
export * from './lib/auth.module';
export * from './lib/services/auth.interceptor';
export * from './lib/services/auth.service';
+export * from './lib/services/error.service';
diff --git a/libs/auth/src/lib/auth.module.ts b/libs/auth/src/lib/auth.module.ts
index 6e6d1ae3..077f1c42 100644
--- a/libs/auth/src/lib/auth.module.ts
+++ b/libs/auth/src/lib/auth.module.ts
@@ -15,6 +15,8 @@ import { OAuthModule } from 'angular-oauth2-oidc';
import { AnchorComponent, ButtonComponent, icons } from '@asset-sg/client-shared';
+import { ErrorService } from './services/error.service';
+
@NgModule({
imports: [
@@ -44,6 +46,9 @@ import { AnchorComponent, ButtonComponent, icons } from '@asset-sg/client-shared
}),
],
exports: [OAuthModule],
- providers: [provideSvgIcons(icons)],
+ providers: [
+ provideSvgIcons(icons),
+ ErrorService,
+ ],
})
export class AuthModule {}
diff --git a/libs/auth/src/lib/services/auth.interceptor.ts b/libs/auth/src/lib/services/auth.interceptor.ts
index ca7d2d32..2a412142 100644
--- a/libs/auth/src/lib/services/auth.interceptor.ts
+++ b/libs/auth/src/lib/services/auth.interceptor.ts
@@ -7,11 +7,14 @@ import { EMPTY, Observable, catchError } from 'rxjs';
import { AlertType, showAlert } from '@asset-sg/client-shared';
+import { ErrorService } from './error.service';
+
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private _oauthService = inject(OAuthService);
private _router: Router = inject(Router);
private readonly store = inject(Store);
+ private readonly errorService = inject(ErrorService);
intercept(req: HttpRequest, next: HttpHandler): Observable> {
const token = sessionStorage.getItem('access_token');
@@ -34,7 +37,9 @@ export class AuthInterceptor implements HttpInterceptor {
} else {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
- if (error.status === 401 || error.status === 403) {
+ if (error.status === 403) {
+ this.errorService.updateMessage(error.error.error);
+ } else if (error.status === 401) {
this.store.dispatch(showAlert({
alert: {
id: `auth-error-${error.status}`,
@@ -44,7 +49,6 @@ export class AuthInterceptor implements HttpInterceptor {
},
}));
} else {
- console.log(error);
this.store.dispatch(showAlert({
alert: {
id: `request-error-${error.status}-${error.url}`,
diff --git a/libs/auth/src/lib/services/error.service.ts b/libs/auth/src/lib/services/error.service.ts
new file mode 100644
index 00000000..6d40b49b
--- /dev/null
+++ b/libs/auth/src/lib/services/error.service.ts
@@ -0,0 +1,19 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+
+@Injectable()
+export class ErrorService {
+ private message$ = new BehaviorSubject(null);
+
+ get onMessage(): Observable {
+ return this.message$.asObservable();
+ }
+
+ updateMessage(message: string) {
+ this.message$.next(message);
+ }
+
+ clear(): void {
+ this.message$.next(null);
+ }
+}
diff --git a/libs/client-shared/src/lib/components/button/button.component.ts b/libs/client-shared/src/lib/components/button/button.component.ts
index 84c70a76..8469f869 100644
--- a/libs/client-shared/src/lib/components/button/button.component.ts
+++ b/libs/client-shared/src/lib/components/button/button.component.ts
@@ -1,72 +1,72 @@
-import { FocusMonitor } from '@angular/cdk/a11y';
-import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
-import {
- AfterViewInit,
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- ElementRef,
- HostBinding,
- Input,
- OnDestroy,
- inject,
-} from '@angular/core';
-
-@Component({
- standalone: true,
- selector:
- // eslint-disable-next-line @angular-eslint/component-selector
- 'button[asset-sg-reset], button[asset-sg-icon-button], button[asset-sg-primary], button[asset-sg-secondary], button[asset-sg-icon-button-tw]',
- template: '',
- styleUrls: ['./button.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class ButtonComponent implements AfterViewInit, OnDestroy {
- protected _host = inject>(ElementRef);
- private _focusMonitor = inject(FocusMonitor);
- private _cd = inject(ChangeDetectorRef);
-
- constructor() {
- const observer = new MutationObserver(mutations => {
- mutations.forEach(mutation => {
- if (mutation.type === 'attributes') {
- if (this._host.nativeElement.attributes.getNamedItem('asset-sg-icon-button-tw')) {
- this._classes = 'bg-white';
- this._cd.detectChanges();
- }
- }
- });
- });
-
- observer.observe(this._host.nativeElement, {
- attributes: true, //configure it to listen to attribute changes
- });
- // console.log(this._host.nativeElement.attributes);
- }
-
- @HostBinding('class')
- private _classes: string | undefined;
-
- private _disabled: true | undefined;
- @HostBinding('[attr.disabled]')
- @HostBinding('[attr.aria-disabled]')
- @Input()
- get disabled(): true | false | undefined {
- return this._disabled;
- }
- set disabled(value: BooleanInput) {
- this._disabled = coerceBooleanProperty(value) || undefined;
- }
-
- @HostBinding('attr.type')
- @Input()
- public type = this._host.nativeElement.tagName === 'BUTTON' ? 'button' : undefined;
-
- ngAfterViewInit() {
- this._focusMonitor.monitor(this._host, false);
- }
-
- ngOnDestroy() {
- this._focusMonitor.stopMonitoring(this._host);
- }
-}
+import { FocusMonitor } from '@angular/cdk/a11y';
+import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';
+import {
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ ElementRef,
+ HostBinding,
+ Input,
+ OnDestroy,
+ inject,
+} from '@angular/core';
+
+@Component({
+ standalone: true,
+ selector:
+ // eslint-disable-next-line @angular-eslint/component-selector
+ 'button[asset-sg-reset], button[asset-sg-icon-button], button[asset-sg-primary], button[asset-sg-warn], button[asset-sg-secondary], button[asset-sg-icon-button-tw]',
+ template: '',
+ styleUrls: ['./button.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ButtonComponent implements AfterViewInit, OnDestroy {
+ protected _host = inject>(ElementRef);
+ private _focusMonitor = inject(FocusMonitor);
+ private _cd = inject(ChangeDetectorRef);
+
+ constructor() {
+ const observer = new MutationObserver(mutations => {
+ mutations.forEach(mutation => {
+ if (mutation.type === 'attributes') {
+ if (this._host.nativeElement.attributes.getNamedItem('asset-sg-icon-button-tw')) {
+ this._classes = 'bg-white';
+ this._cd.detectChanges();
+ }
+ }
+ });
+ });
+
+ observer.observe(this._host.nativeElement, {
+ attributes: true, //configure it to listen to attribute changes
+ });
+ // console.log(this._host.nativeElement.attributes);
+ }
+
+ @HostBinding('class')
+ private _classes: string | undefined;
+
+ private _disabled: true | undefined;
+ @HostBinding('[attr.disabled]')
+ @HostBinding('[attr.aria-disabled]')
+ @Input()
+ get disabled(): true | false | undefined {
+ return this._disabled;
+ }
+ set disabled(value: BooleanInput) {
+ this._disabled = coerceBooleanProperty(value) || undefined;
+ }
+
+ @HostBinding('attr.type')
+ @Input()
+ public type = this._host.nativeElement.tagName === 'BUTTON' ? 'button' : undefined;
+
+ ngAfterViewInit() {
+ this._focusMonitor.monitor(this._host, false);
+ }
+
+ ngOnDestroy() {
+ this._focusMonitor.stopMonitoring(this._host);
+ }
+}