diff --git a/apps/client-asset-sg/project.json b/apps/client-asset-sg/project.json index 1ce5ea48..acc6be8d 100644 --- a/apps/client-asset-sg/project.json +++ b/apps/client-asset-sg/project.json @@ -43,14 +43,6 @@ } ] }, - "int": { - "fileReplacements": [ - { - "replace": "apps/client-asset-sg/src/environments/environment.ts", - "with": "apps/client-asset-sg/src/environments/environment.int.ts" - } - ] - }, "development": { "buildOptimizer": false, "optimization": false, @@ -68,9 +60,6 @@ "production": { "buildTarget": "client-asset-sg:build:production" }, - "int": { - "buildTarget": "client-asset-sg:build:int" - }, "development": { "buildTarget": "client-asset-sg:build:development" } diff --git a/apps/client-asset-sg/src/app/app.component.ts b/apps/client-asset-sg/src/app/app.component.ts index 0b1ac07a..d8498673 100644 --- a/apps/client-asset-sg/src/app/app.component.ts +++ b/apps/client-asset-sg/src/app/app.component.ts @@ -1,12 +1,13 @@ import { HttpClient } from '@angular/common/http'; import { Component, inject } from '@angular/core'; -import { AuthService, AuthState, ErrorService } from '@asset-sg/auth'; import { AppPortalService, appSharedStateActions, setCssCustomProperties } from '@asset-sg/client-shared'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { Store } from '@ngrx/store'; import { WINDOW } from 'ngx-window-token'; import { debounceTime, fromEvent, startWith, switchMap } from 'rxjs'; import { assert } from 'tsafe'; +import { AuthService, AuthState } from './features/auth/auth.service'; +import { ErrorService } from './features/auth/error.service'; import { AppState } from './state/app-state'; const fullHdWidth = 1920; diff --git a/apps/client-asset-sg/src/app/app.module.ts b/apps/client-asset-sg/src/app/app.module.ts index 87ca5cd0..2c9b146a 100644 --- a/apps/client-asset-sg/src/app/app.module.ts +++ b/apps/client-asset-sg/src/app/app.module.ts @@ -5,6 +5,8 @@ import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import locale_deCH from '@angular/common/locales/de-CH'; import { inject, NgModule } from '@angular/core'; import { MatButton } from '@angular/material/button'; +import { MatDialogActions, MatDialogContent } from '@angular/material/dialog'; +import { MatDivider } from '@angular/material/divider'; import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; import { MatMenu, MatMenuTrigger } from '@angular/material/menu'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @@ -12,7 +14,6 @@ import { MatTooltip } from '@angular/material/tooltip'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule } from '@angular/router'; -import { AuthInterceptor, AuthModule, ErrorService } from '@asset-sg/auth'; import { AdminOnlyDirective, AlertModule, @@ -23,6 +24,7 @@ import { CURRENT_LANG, currentLangFactory, icons, + LanguageSelectorComponent, TranslateTsLoader, } from '@asset-sg/client-shared'; import { storeLogger } from '@asset-sg/core'; @@ -34,15 +36,18 @@ import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-transla import { ForModule } from '@rx-angular/template/for'; import { LetModule } from '@rx-angular/template/let'; import { PushModule } from '@rx-angular/template/push'; - import { environment } from '../environments/environment'; import { adminGuard } from './app-guards'; import { AppComponent } from './app.component'; import { AppBarComponent, MenuBarComponent, NotFoundComponent, RedirectToLangComponent } from './components'; +import { DisclaimerDialogComponent } from './components/disclaimer-dialog/disclaimer-dialog.component'; import { MenuBarItemComponent } from './components/menu-bar-item/menu-bar-item.component'; import { SplashScreenComponent } from './components/splash-screen/splash-screen.component'; +import { AuthModule } from './features/auth/auth.module'; +import { ErrorService } from './features/auth/error.service'; import { appTranslations } from './i18n'; +import { HttpInterceptor } from './services/http.interceptor'; import { AppSharedStateEffects } from './state'; import { appSharedStateReducer } from './state/app-shared.reducer'; @@ -57,10 +62,12 @@ registerLocaleData(locale_deCH, 'de-CH'); MenuBarComponent, MenuBarItemComponent, SplashScreenComponent, + DisclaimerDialogComponent, ], imports: [ CommonModule, BrowserModule, + AuthModule, BrowserAnimationsModule, HttpClientModule, RouterModule.forRoot([ @@ -120,11 +127,15 @@ registerLocaleData(locale_deCH, 'de-CH'); MatButton, MatMenuTrigger, MatMenu, + LanguageSelectorComponent, + MatDivider, + MatDialogContent, + MatDialogActions, ], providers: [ provideSvgIcons(icons), ErrorService, - { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: HttpInterceptor, multi: true }, { provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: { appearance: 'fill', floatLabel: 'auto' } }, { provide: CURRENT_LANG, useFactory: currentLangFactory }, ], diff --git a/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.html b/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.html index f835fe89..53eb8a91 100644 --- a/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.html +++ b/apps/client-asset-sg/src/app/components/app-bar/app-bar.component.html @@ -5,31 +5,7 @@
{{ version }} - - - - @for (language of languages; track language.lang) { - - - - - - {{ language.lang }} - - - } - - + + + diff --git a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.scss b/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.scss new file mode 100644 index 00000000..6e731eb9 --- /dev/null +++ b/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.scss @@ -0,0 +1,34 @@ +::ng-deep .cdk-overlay-dark-backdrop { + background-color: grey; +} + +h1 { + color: black; + font-weight: 500; +} + +h2 { + margin: 0; +} + +p { + margin: 0; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 24px 24px 16px 24px; +} + +.content { + padding: 24px; +} + +.actions { + display: flex; + justify-content: flex-end; + padding: 24px; + align-items: center; +} diff --git a/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts b/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts new file mode 100644 index 00000000..423ce49a --- /dev/null +++ b/apps/client-asset-sg/src/app/components/disclaimer-dialog/disclaimer-dialog.component.ts @@ -0,0 +1,31 @@ +import { Component, inject } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; +import { CURRENT_LANG } from '@asset-sg/client-shared'; +import { TranslateService } from '@ngx-translate/core'; +import { map } from 'rxjs'; + +const LEGAL_BASE_URL = 'https://www.swissgeol.ch/datenschutz'; + +@Component({ + selector: 'asset-sg-disclaimer-dialog', + templateUrl: './disclaimer-dialog.component.html', + styleUrl: './disclaimer-dialog.component.scss', +}) +export class DisclaimerDialogComponent { + public text = ''; + private readonly dialogRef = inject(MatDialogRef); + private readonly translateService = inject(TranslateService); + + private readonly currentLang$ = inject(CURRENT_LANG); + public readonly legalUrl$ = this.currentLang$.pipe( + map((lang) => (lang === 'de' ? LEGAL_BASE_URL : `${LEGAL_BASE_URL}-${lang}/`)) + ); + + public readonly disclaimerText$ = this.currentLang$.pipe( + map(() => this.translateService.instant(`disclaimer.content`).replaceAll('\n', '
')) + ); + + public close() { + this.dialogRef.close(); + } +} diff --git a/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts b/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts index 42561142..b48ec478 100644 --- a/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts +++ b/apps/client-asset-sg/src/app/components/redirect-to-lang/redirect-to-lang.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { AuthService } from '@asset-sg/auth'; import { UntilDestroy } from '@ngneat/until-destroy'; +import { AuthService } from '../../features/auth/auth.service'; @UntilDestroy() @Component({ diff --git a/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts b/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts index 440cd2de..3d0d743b 100644 --- a/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts +++ b/apps/client-asset-sg/src/app/components/splash-screen/splash-screen.component.ts @@ -1,7 +1,7 @@ import { Component, inject } from '@angular/core'; -import { AuthService, AuthState } from '@asset-sg/auth'; import { CURRENT_LANG } from '@asset-sg/client-shared'; import { TranslateService } from '@ngx-translate/core'; +import { AuthService, AuthState } from '../../features/auth/auth.service'; @Component({ selector: 'app-splash-screen', diff --git a/libs/auth/src/lib/auth.module.ts b/apps/client-asset-sg/src/app/features/auth/auth.module.ts similarity index 89% rename from libs/auth/src/lib/auth.module.ts rename to apps/client-asset-sg/src/app/features/auth/auth.module.ts index 2681b5a8..4a66ecda 100644 --- a/libs/auth/src/lib/auth.module.ts +++ b/apps/client-asset-sg/src/app/features/auth/auth.module.ts @@ -13,7 +13,8 @@ import { LetModule } from '@rx-angular/template/let'; import { PushModule } from '@rx-angular/template/push'; import { OAuthModule } from 'angular-oauth2-oidc'; -import { ErrorService } from './services/error.service'; +import { HttpInterceptor } from '../../services/http.interceptor'; +import { ErrorService } from './error.service'; @NgModule({ imports: [ @@ -43,6 +44,6 @@ import { ErrorService } from './services/error.service'; }), ], exports: [OAuthModule], - providers: [provideSvgIcons(icons), ErrorService], + providers: [provideSvgIcons(icons), ErrorService, HttpInterceptor], }) export class AuthModule {} diff --git a/libs/auth/src/lib/services/auth.service.ts b/apps/client-asset-sg/src/app/features/auth/auth.service.ts similarity index 89% rename from libs/auth/src/lib/services/auth.service.ts rename to apps/client-asset-sg/src/app/features/auth/auth.service.ts index 73344da8..1a29017f 100644 --- a/libs/auth/src/lib/services/auth.service.ts +++ b/apps/client-asset-sg/src/app/features/auth/auth.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { ApiError, appSharedStateActions, AppState } from '@asset-sg/client-shared'; import { ORD } from '@asset-sg/core'; @@ -9,6 +10,8 @@ import { Store } from '@ngrx/store'; import { OAuthService } from 'angular-oauth2-oidc'; import { plainToInstance } from 'class-transformer'; import { BehaviorSubject, map, Observable, startWith } from 'rxjs'; +import { environment } from '../../../environments/environment'; +import { DisclaimerDialogComponent } from '../../components/disclaimer-dialog/disclaimer-dialog.component'; @Injectable({ providedIn: 'root' }) export class AuthService { @@ -16,6 +19,7 @@ export class AuthService { private readonly oauthService = inject(OAuthService); private readonly store = inject(Store); private readonly router = inject(Router); + private readonly dialogService = inject(MatDialog); private readonly _state$ = new BehaviorSubject(AuthState.Ongoing); private readonly _isInitialized$ = new BehaviorSubject(false); @@ -56,6 +60,13 @@ export class AuthService { // If something else has interrupted the auth process, then we don't want to signal a success. if (this._state$.value === AuthState.Ongoing) { this._state$.next(AuthState.Success); + + if (!environment.hideDisclaimer) { + this.dialogService.open(DisclaimerDialogComponent, { + width: '500px', + disableClose: true, + }); + } } } } else { diff --git a/libs/auth/src/lib/services/error.service.ts b/apps/client-asset-sg/src/app/features/auth/error.service.ts similarity index 100% rename from libs/auth/src/lib/services/error.service.ts rename to apps/client-asset-sg/src/app/features/auth/error.service.ts diff --git a/apps/client-asset-sg/src/app/i18n/de.ts b/apps/client-asset-sg/src/app/i18n/de.ts index 4208803b..ab467b6a 100644 --- a/apps/client-asset-sg/src/app/i18n/de.ts +++ b/apps/client-asset-sg/src/app/i18n/de.ts @@ -288,4 +288,17 @@ export const deAppTranslations = { addUsers: 'Benutzer hinzufügen', }, }, + disclaimer: { + title: 'Nutzungsbedingungen', + subtitle: 'Haftung', + content: + 'Obwohl das Bundesamt für Landestopografie swisstopo mit aller Sorgfalt auf die Richtigkeit der veröffentlichten Informationen achtet, kann hinsichtlich der inhaltlichen Richtigkeit, Genauigkeit, Aktualität, Zuverlässigkeit und Vollständigkeit dieser Informationen keine Gewährleistung übernommen werden.\n' + + '\n' + + 'Swisstopo behält sich ausdrücklich vor, jederzeit Inhalte ohne Ankündigung ganz oder teilweise zu ändern, zu löschen oder zeitweise nicht zu veröffentlichen.\n' + + '\n' + + 'Haftungsansprüche gegen swisstopo wegen Schäden materieller oder immaterieller Art, welche aus dem Zugriff oder der Nutzung bzw. Nichtnutzung der veröffentlichten Informationen, durch Missbrauch der Verbindung oder durch technische Störungen entstanden sind, werden ausgeschlossen.\n' + + '\n' + + 'Weitere rechtliche Bestimmungen finden sie hier:', + accept: 'Akzeptieren', + }, }; diff --git a/apps/client-asset-sg/src/app/i18n/en.ts b/apps/client-asset-sg/src/app/i18n/en.ts index 96d50d6e..25516501 100644 --- a/apps/client-asset-sg/src/app/i18n/en.ts +++ b/apps/client-asset-sg/src/app/i18n/en.ts @@ -289,4 +289,17 @@ export const enAppTranslations: AppTranslations = { addUsers: 'Add users', }, }, + disclaimer: { + title: 'Terms of service', + subtitle: 'Limitation of liability', + content: + 'Although every care has been taken by the Federal Office of Topography swisstopo to ensure the accuracy of the information published, no guarantee can be given with regard to the accurate, reliable, up-to-date or complete nature of this information.\n' + + '\n' + + 'swisstopo reserves the right to alter or remove the content, in full or in part, without prior notice.\n' + + '\n' + + 'Liability claims against swisstopo for material or immaterial damage resulting from access to or use or non-use of the published information, from misuse of the connection or from technical faults are excluded.\n' + + '\n' + + 'Further legal provisions can be found here:', + accept: 'Accept', + }, }; diff --git a/apps/client-asset-sg/src/app/i18n/fr.ts b/apps/client-asset-sg/src/app/i18n/fr.ts index 7d398311..f758d055 100644 --- a/apps/client-asset-sg/src/app/i18n/fr.ts +++ b/apps/client-asset-sg/src/app/i18n/fr.ts @@ -290,4 +290,17 @@ export const frAppTranslations: AppTranslations = { addUsers: 'Ajouter des utilisateurs', }, }, + disclaimer: { + title: "Conditions d'utilisation", + subtitle: 'Responsabilité', + content: + 'Malgré la grande attention qu’il porte à la justesse des informations diffusées sur ce site, l’Office fédéral de topographie swisstopo ne peut endosser aucune responsabilité quant à la fidélité, à l’exactitude, à l’actualité, à la fiabilité et à l’intégralité de ces informations.\n' + + '\n' + + 'swisstopo se réserve expressément le droit de modifier en partie ou en totalité le contenu de ce site, de le supprimer ou d’en suspendre temporairement la diffusion, et ce à tout moment et sans avertissement préalable.\n' + + '\n' + + 'swisstopo ne saurait être tenu pour responsable des dommages matériels ou immatériels qui pourraient être causés par l’accès aux informations diffusées ou par leur utilisation ou non-utilisation, par le mauvais usage de la connexion ou par des problèmes techniques.\n' + + '\n' + + "Pour plus d'informations légales, veuillez consulter le lien suivant:", + accept: 'Accepter', + }, }; diff --git a/apps/client-asset-sg/src/app/i18n/it.ts b/apps/client-asset-sg/src/app/i18n/it.ts index b49f2daa..cd12fb43 100644 --- a/apps/client-asset-sg/src/app/i18n/it.ts +++ b/apps/client-asset-sg/src/app/i18n/it.ts @@ -289,4 +289,17 @@ export const itAppTranslations: AppTranslations = { addUsers: 'IT Benutzer hinzufügen', }, }, + disclaimer: { + title: 'Condizioni di utilizzo', + subtitle: 'Responsabilità', + content: + 'Nonostante si presti grande attenzione all’esattezza delle informazioni pubblicate su questo sito, l’Ufficio federale di topografia swisstopo declina ogni responsabilità per la fedeltà, l’esattezza, l’attualità, l’affidabilità e la completezza di tali informazioni.\n' + + '\n' + + 'swisstopo si riserva esplicitamente il diritto in qualsiasi momento di modificare parzialmente o completamente il contenuto del sito, di cancellarlo o di sospenderne temporaneamente la pubblicazione, senza alcun preavviso.\n' + + '\n' + + 'swisstopo declina ogni responsabilità per danni materiali o immateriali derivanti dall’accesso alle informazioni diffuse, dall’uso o dal mancato uso del sito, oppure che sono riconducibili a un malfunzionamento del collegamento o a disturbi tecnici del sito.\n' + + '\n' + + 'Per ulteriori informazioni legali, consultare il seguente link:', + accept: 'Accettare', + }, }; diff --git a/apps/client-asset-sg/src/app/i18n/rm.ts b/apps/client-asset-sg/src/app/i18n/rm.ts index e81baeb6..45bfb3f9 100644 --- a/apps/client-asset-sg/src/app/i18n/rm.ts +++ b/apps/client-asset-sg/src/app/i18n/rm.ts @@ -289,4 +289,17 @@ export const rmAppTranslations: AppTranslations = { addUsers: 'RM Benutzer hinzufügen', }, }, + disclaimer: { + title: 'RM Nutzungsbedingungen', + subtitle: 'RM Haftung', + content: + 'RM Obwohl das Bundesamt für Landestopografie swisstopo mit aller Sorgfalt auf die Richtigkeit der veröffentlichten Informationen achtet, kann hinsichtlich der inhaltlichen Richtigkeit, Genauigkeit, Aktualität, Zuverlässigkeit und Vollständigkeit dieser Informationen keine Gewährleistung übernommen werden.\n' + + '\n' + + 'Swisstopo behält sich ausdrücklich vor, jederzeit Inhalte ohne Ankündigung ganz oder teilweise zu ändern, zu löschen oder zeitweise nicht zu veröffentlichen.\n' + + '\n' + + 'Haftungsansprüche gegen swisstopo wegen Schäden materieller oder immaterieller Art, welche aus dem Zugriff oder der Nutzung bzw. Nichtnutzung der veröffentlichten Informationen, durch Missbrauch der Verbindung oder durch technische Störungen entstanden sind, werden ausgeschlossen.\n' + + '\n' + + 'Weitere rechtliche Bestimmungen finden sie hier:', + accept: 'RM Akzeptieren', + }, }; diff --git a/libs/auth/src/lib/services/auth.interceptor.ts b/apps/client-asset-sg/src/app/services/http.interceptor.ts similarity index 95% rename from libs/auth/src/lib/services/auth.interceptor.ts rename to apps/client-asset-sg/src/app/services/http.interceptor.ts index b669ae87..b8c15669 100644 --- a/libs/auth/src/lib/services/auth.interceptor.ts +++ b/apps/client-asset-sg/src/app/services/http.interceptor.ts @@ -1,4 +1,4 @@ -import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http'; import { inject, Injectable, OnDestroy } from '@angular/core'; import { NavigationCancel, NavigationEnd, NavigationError, NavigationStart, Router } from '@angular/router'; import { AlertType, fromAppShared, showAlert } from '@asset-sg/client-shared'; @@ -6,10 +6,10 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { catchError, EMPTY, from, Observable, Subscription, switchMap } from 'rxjs'; -import { AuthService, AuthState } from './auth.service'; +import { AuthService, AuthState } from '../features/auth/auth.service'; @Injectable() -export class AuthInterceptor implements HttpInterceptor, OnDestroy { +export class HttpInterceptor implements HttpInterceptor, OnDestroy { private _oauthService = inject(OAuthService); private readonly store = inject(Store); private readonly authService = inject(AuthService); diff --git a/apps/client-asset-sg/src/app/state/app.effects.ts b/apps/client-asset-sg/src/app/state/app.effects.ts index 0c9c0738..5f088aed 100644 --- a/apps/client-asset-sg/src/app/state/app.effects.ts +++ b/apps/client-asset-sg/src/app/state/app.effects.ts @@ -1,6 +1,5 @@ import { inject, Injectable } from '@angular/core'; import { NavigationEnd, Router, RouterStateSnapshot } from '@angular/router'; -import { AuthService } from '@asset-sg/auth'; import { appSharedStateActions, fromAppShared } from '@asset-sg/client-shared'; import { ORD } from '@asset-sg/core'; import { eqLangRight, Lang } from '@asset-sg/shared'; @@ -11,6 +10,7 @@ import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; import * as E from 'fp-ts/Either'; import { combineLatest, distinctUntilChanged, filter, map, switchMap, take } from 'rxjs'; +import { AuthService } from '../features/auth/auth.service'; import { AppSharedStateService } from './app-shared-state.service'; import { AppState } from './app-state'; diff --git a/apps/client-asset-sg/src/environments/environment-type.ts b/apps/client-asset-sg/src/environments/environment-type.ts index fffd32d4..fed622c2 100644 --- a/apps/client-asset-sg/src/environments/environment-type.ts +++ b/apps/client-asset-sg/src/environments/environment-type.ts @@ -1,3 +1,4 @@ export interface CompileTimeEnvironment { ngrxStoreLoggerEnabled: boolean; + hideDisclaimer: boolean; } diff --git a/apps/client-asset-sg/src/environments/environment.int.ts b/apps/client-asset-sg/src/environments/environment.int.ts deleted file mode 100644 index a1267e9c..00000000 --- a/apps/client-asset-sg/src/environments/environment.int.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CompileTimeEnvironment } from './environment-type'; - -export const environment: CompileTimeEnvironment = { - ngrxStoreLoggerEnabled: false, -}; diff --git a/apps/client-asset-sg/src/environments/environment.prod.ts b/apps/client-asset-sg/src/environments/environment.prod.ts index a1267e9c..8b5f7283 100644 --- a/apps/client-asset-sg/src/environments/environment.prod.ts +++ b/apps/client-asset-sg/src/environments/environment.prod.ts @@ -2,4 +2,5 @@ import { CompileTimeEnvironment } from './environment-type'; export const environment: CompileTimeEnvironment = { ngrxStoreLoggerEnabled: false, + hideDisclaimer: false, }; diff --git a/apps/client-asset-sg/src/environments/environment.ts b/apps/client-asset-sg/src/environments/environment.ts index a1267e9c..32dd082d 100644 --- a/apps/client-asset-sg/src/environments/environment.ts +++ b/apps/client-asset-sg/src/environments/environment.ts @@ -2,4 +2,5 @@ import { CompileTimeEnvironment } from './environment-type'; export const environment: CompileTimeEnvironment = { ngrxStoreLoggerEnabled: false, + hideDisclaimer: true, }; diff --git a/apps/client-asset-sg/src/styles.scss b/apps/client-asset-sg/src/styles.scss index bfc4bdea..2409dea5 100644 --- a/apps/client-asset-sg/src/styles.scss +++ b/apps/client-asset-sg/src/styles.scss @@ -80,41 +80,51 @@ input { * { scrollbar-width: auto; scrollbar-color: transparent; + &::-webkit-scrollbar { width: 1.5rem; height: 1.5rem; } + &::-webkit-scrollbar-button { background-color: transparent; + &:vertical:decrement, &:vertical:increment { height: 1px; } + &:horizontal:decrement, &:horizontal:increment { width: 1px; } } + &::-webkit-scrollbar-track { background-color: transparent; border-color: transparent; border-style: solid; background-clip: content-box; + &:vertical { border-width: 0 0.5625rem 0 0.5625rem; } + &:horizontal { border-width: 0.5625rem 0 0.5625rem 0; } } + &::-webkit-scrollbar-thumb { background-color: #bcc1c6; border-color: transparent; border-style: solid; background-clip: content-box; + &:vertical { border-width: 0 0.5625rem 0 0.5625rem; } + &:horizontal { border-width: 0.5625rem 0 0.5625rem 0; } @@ -136,9 +146,11 @@ input { .button-area { display: flex; justify-content: flex-start; + button { min-width: 6rem; } + button + button { margin-left: 1.5rem; } @@ -168,27 +180,34 @@ input { .relative { position: relative; } + .absolute { position: absolute; } + .\!absolute { position: absolute !important; } + .inset-0 { top: 0; right: 0; bottom: 0; left: 0; } + .left-0 { left: 0; } + .top-0 { top: 0; } + .right-0 { right: 0; } + .opacity-0 { opacity: 0; } @@ -196,12 +215,15 @@ input { .border-2 { border-width: 2px; } + .border-dashed { border-style: dashed; } + .border-cyan-09 { border-color: variables.$cyan-09; } + .border-grey-03 { border-color: variables.$grey-03; } @@ -209,6 +231,7 @@ input { .rounded { border-radius: 0.25rem; } + .cursor-pointer { cursor: pointer; } @@ -220,6 +243,7 @@ input { .t-1\.5 { top: 0.375rem; } + .r-3 { right: 0.75rem; } @@ -231,21 +255,27 @@ input { .flex { display: flex; } + .flex-row { flex-direction: row; } + .flex-column { flex-direction: column; } + .justify-between { justify-content: space-between; } + .self-start { align-self: flex-start; } + .self-center { align-self: center; } + .self-end { align-self: flex-end; } @@ -257,12 +287,15 @@ input { .basis-full { flex-basis: 100%; } + .basis-10 { flex-basis: 2.5rem; } + .basis-24 { flex-basis: 6rem; } + .basis-32 { flex-basis: 8rem; } @@ -270,12 +303,15 @@ input { .shrink { flex-shrink: 1; } + .shrink-0 { flex-shrink: 0; } + .grow { flex-grow: 1; } + .grow-0 { flex-grow: 0; } @@ -283,9 +319,11 @@ input { .gap-3 { gap: 0.75rem; } + .gap-4 { gap: 1rem; } + .gap-6 { gap: 1.5rem; } @@ -297,38 +335,49 @@ input { .bg-white { background-color: variables.$white; } + .bg-cyan-01 { background-color: variables.$cyan-01; } + .hover\:bg-cyan-06 { &:hover { background-color: variables.$cyan-06; } } + .bg-cyan-09 { background-color: variables.$cyan-09; } + .bg-grey-00 { background-color: variables.$grey-00; } + .hover\:bg-grey-03 { background-color: variables.$grey-03; } + .bg-grey-03 { background-color: variables.$grey-03; } + .bg-grey-05 { background-color: variables.$grey-05; } + .bg-grey-07 { background-color: variables.$grey-07; } + .bg-grey-09 { background-color: variables.$grey-09; } + .bg-orange-01 { background-color: variables.$orange-01; } + .bg-red-01 { background-color: variables.$red-01; } @@ -356,10 +405,12 @@ input { .ml-0 { margin-left: 0; } + .mx-0 { margin-left: 0; margin-right: 0; } + .my-0 { margin-top: 0; margin-bottom: 0; @@ -372,22 +423,28 @@ input { .m-1 { margin: 0.25rem; } + .mt-1 { margin-top: 0.25rem; } + .mb-1 { margin-bottom: 0.25rem; } + .mr-1 { margin-right: 0.25rem; } + .ml-1 { margin-left: 0.25rem; } + .mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; } + .-ml-1 { margin-left: -0.25rem; } @@ -395,28 +452,36 @@ input { .m-2 { margin: 0.5rem; } + .mt-2 { margin-top: 0.5rem; } + .mb-2 { margin-bottom: 0.5rem; } + .mr-2 { margin-right: 0.5rem; } + .ml-2 { margin-left: 0.5rem; } + .mx-2 { margin-left: 0.5rem; margin-right: 0.5rem; } + .-mt-2 { margin-top: -0.5rem; } + .-ml-2 { margin-left: -0.5rem; } + .-mr-2 { margin-right: -0.5rem; } @@ -428,15 +493,19 @@ input { .m-3 { margin: 0.75rem; } + .mt-3 { margin-top: 0.75rem; } + .mb-3 { margin-bottom: 0.75rem; } + .mr-3 { margin-right: 0.75rem; } + .ml-3 { margin-left: 0.75rem; } @@ -444,22 +513,28 @@ input { .m-4 { margin: 1rem; } + .mt-4 { margin-top: 1rem; } + .mb-4 { margin-bottom: 1rem; } + .mr-4 { margin-right: 1rem; } + .ml-4 { margin-left: 1rem; } + .mx-4 { margin-left: 1rem; margin-right: 1rem; } + .my-4 { margin-top: 1rem; margin-bottom: 1rem; @@ -468,15 +543,19 @@ input { .-m-4 { margin: -1rem; } + .-mt-4 { margin-top: -1rem; } + .-mb-4 { margin-bottom: -1rem; } + .-mr-4 { margin-right: -1rem; } + .-ml-4 { margin-left: -1rem; } @@ -484,26 +563,33 @@ input { .m-5 { margin: 1.25rem; } + .mt-5 { margin-top: 1.25rem; } + .mb-5 { margin-bottom: 1.25rem; } + .mr-5 { margin-right: 1.25rem; } + .ml-5 { margin-left: 1.25rem; } + .mx-5 { margin-left: 1.25rem; margin-right: 1.25rem; } + .my-5 { margin-top: 1.25rem; margin-bottom: 1.25rem; } + .-mr-5 { margin-right: -1.25rem; } @@ -511,18 +597,23 @@ input { .m-6 { margin: 1.5rem; } + .mt-6 { margin-top: 1.5rem; } + .mb-6 { margin-bottom: 1.5rem; } + .mr-6 { margin-right: 1.5rem; } + .ml-6 { margin-left: 1.5rem; } + .-mr-6 { margin-right: -1.5rem; } @@ -530,15 +621,19 @@ input { .m-8 { margin: 2rem; } + .mt-8 { margin-top: 2rem; } + .mb-8 { margin-bottom: 2rem; } + .mr-8 { margin-right: 2rem; } + .ml-8 { margin-left: 2rem; } @@ -546,22 +641,28 @@ input { .m-10 { margin: 2.5rem; } + .mt-10 { margin-top: 2.5rem; } + .mb-10 { margin-bottom: 2.5rem; } + .mr-10 { margin-right: 2.5rem; } + .ml-10 { margin-left: 2.5rem; } + .mx-10 { margin-left: 2.5rem; margin-right: 2.5rem; } + .my-10 { margin-top: 2.5rem; margin-bottom: 2.5rem; @@ -570,22 +671,28 @@ input { .m-12 { margin: 3rem; } + .mt-12 { margin-top: 3rem; } + .mb-12 { margin-bottom: 3rem; } + .mr-12 { margin-right: 3rem; } + .ml-12 { margin-left: 3rem; } + .mx-12 { margin-left: 3rem; margin-right: 3rem; } + .my-12 { margin-top: 3rem; margin-bottom: 3rem; @@ -594,22 +701,28 @@ input { .p-0 { padding: 0; } + .pt-0 { padding-top: 0; } + .pb-0 { padding-bottom: 0; } + .pr-0 { padding-right: 0; } + .pl-0 { padding-left: 0; } + .py-0 { padding-top: 0; padding-bottom: 0; } + .px-0 { padding-left: 0; padding-right: 0; @@ -618,22 +731,28 @@ input { .p-1 { padding: 0.25rem; } + .pt-1 { padding-top: 0.25rem; } + .pb-1 { padding-bottom: 0.25rem; } + .pr-1 { padding-right: 0.25rem; } + .pl-1 { padding-left: 0.25rem; } + .py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; } + .px-1 { padding-left: 0.25rem; padding-right: 0.25rem; @@ -642,22 +761,28 @@ input { .p-2 { padding: 0.5rem; } + .pt-2 { padding-top: 0.5rem; } + .pb-2 { padding-bottom: 0.5rem; } + .pr-2 { padding-right: 0.5rem; } + .pl-2 { padding-left: 0.5rem; } + .py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } + .px-2 { padding-left: 0.5rem; padding-right: 0.5rem; @@ -666,22 +791,28 @@ input { .p-3 { padding: 0.75rem; } + .pt-3 { padding-top: 0.75rem; } + .pb-3 { padding-bottom: 0.75rem; } + .pr-3 { padding-right: 0.75rem; } + .pl-3 { padding-left: 0.75rem; } + .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; } + .px-3 { padding-left: 0.75rem; padding-right: 0.75rem; @@ -690,22 +821,28 @@ input { .p-4 { padding: 1rem; } + .pt-4 { padding-top: 1rem; } + .pb-4 { padding-bottom: 1rem; } + .pr-4 { padding-right: 1rem; } + .pl-4 { padding-left: 1rem; } + .py-4 { padding-top: 1rem; padding-bottom: 1rem; } + .px-4 { padding-left: 1rem; padding-right: 1rem; @@ -714,22 +851,28 @@ input { .p-6 { padding: 1.5rem; } + .pt-6 { padding-top: 1.5rem; } + .pb-6 { padding-bottom: 1.5rem; } + .pr-6 { padding-right: 1.5rem; } + .pl-6 { padding-left: 1.5rem; } + .py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; } + .px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -738,22 +881,28 @@ input { .p-8 { padding: 2rem; } + .pt-8 { padding-top: 2rem; } + .pb-8 { padding-bottom: 2rem; } + .pr-8 { padding-right: 2rem; } + .pl-8 { padding-left: 2rem; } + .py-8 { padding-top: 2rem; padding-bottom: 2rem; } + .px-8 { padding-left: 2rem; padding-right: 2rem; @@ -762,22 +911,28 @@ input { .p-10 { padding: 2.5rem; } + .pt-10 { padding-top: 2.5rem; } + .pb-10 { padding-bottom: 2.5rem; } + .pr-10 { padding-right: 2.5rem; } + .pl-10 { padding-left: 2.5rem; } + .py-10 { padding-top: 2.5rem; padding-bottom: 2.5rem; } + .px-10 { padding-left: 2.5rem; padding-right: 2.5rem; @@ -794,12 +949,15 @@ input { .font-normal { font-weight: variables.$font-normal; } + .font-medium { font-weight: variables.$font-medium; } + .font-semibold { font-weight: variables.$font-semibold; } + .font-bold { font-weight: variables.$font-bold; } @@ -811,12 +969,15 @@ input { .text-red { color: variables.$red; } + .text-dark-red { color: variables.$dark-red; } + .text-cyan-09 { color: variables.$cyan-09; } + .text-grey-08 { color: variables.$grey-08; } @@ -824,15 +985,19 @@ input { .h-1 { height: 0.25rem; } + .h-10 { height: 2.5rem; } + .h-full { height: 100%; } + .max-h-48 { max-height: 12rem; } + .max-h-full { max-height: 100%; } @@ -840,18 +1005,23 @@ input { .w-2 { width: 0.5rem; } + .w-4 { width: 1rem; } + .w-7 { width: 1.75rem; } + .w-8 { width: 2rem; } + .w-12 { width: 3rem; } + .w-full { width: 100%; } @@ -867,9 +1037,11 @@ input { .scroll-smooth { scroll-behavior: smooth; } + .overflow-x-hidden { overflow-x: hidden; } + .overflow-y-scroll { overflow-y: scroll; } diff --git a/libs/auth/README.md b/libs/auth/README.md deleted file mode 100644 index fa43269e..00000000 --- a/libs/auth/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# auth - -This library was generated with [Nx](https://nx.dev). - -## Running unit tests - -Run `nx test auth` to execute the unit tests. diff --git a/libs/auth/eslint.config.js b/libs/auth/eslint.config.js deleted file mode 100644 index df7cfc23..00000000 --- a/libs/auth/eslint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -const baseConfig = require('../../eslint.config.js'); - -module.exports = [...baseConfig]; diff --git a/libs/auth/jest.config.ts b/libs/auth/jest.config.ts deleted file mode 100644 index 13ffce65..00000000 --- a/libs/auth/jest.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'auth', - preset: '../../jest.preset.js', - setupFilesAfterEnv: ['/src/test-setup.ts'], - globals: {}, - coverageDirectory: '../../coverage/libs/auth', - transform: { - '^.+\\.(ts|mjs|js|html)$': [ - 'jest-preset-angular', - { - tsconfig: '/tsconfig.spec.json', - stringifyContentPathRegex: '\\.(html|svg)$', - }, - ], - }, - transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], - snapshotSerializers: [ - 'jest-preset-angular/build/serializers/no-ng-attributes', - 'jest-preset-angular/build/serializers/ng-snapshot', - 'jest-preset-angular/build/serializers/html-comment', - ], -}; diff --git a/libs/auth/ng-package.json b/libs/auth/ng-package.json deleted file mode 100644 index 91c27b24..00000000 --- a/libs/auth/ng-package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", - "dest": "../../dist/libs/auth", - "lib": { - "entryFile": "src/index.ts" - } -} diff --git a/libs/auth/package.json b/libs/auth/package.json deleted file mode 100644 index 6b874fa3..00000000 --- a/libs/auth/package.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "@asset-sg/auth", - "version": "0.0.1", - "peerDependencies": { - "@angular/common": "^15.0.0", - "@angular/core": "^15.0.0" - }, - "dependencies": { - "tslib": "^2.3.0" - } -} diff --git a/libs/auth/project.json b/libs/auth/project.json deleted file mode 100644 index 4953c542..00000000 --- a/libs/auth/project.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "auth", - "$schema": "../../node_modules/nx/schemas/project-schema.json", - "projectType": "library", - "sourceRoot": "libs/auth/src", - "prefix": "asset-sg", - "tags": [], - "targets": { - "build": { - "executor": "@nx/angular:ng-packagr-lite", - "outputs": ["{workspaceRoot}/dist/{projectRoot}"], - "options": { - "project": "libs/auth/ng-package.json", - "tsConfig": "libs/auth/tsconfig.lib.json" - }, - "configurations": { - "production": { - "tsConfig": "libs/auth/tsconfig.lib.prod.json" - }, - "development": { - "tsConfig": "libs/auth/tsconfig.lib.json" - } - }, - "defaultConfiguration": "production" - }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], - "options": { - "jestConfig": "libs/auth/jest.config.ts" - } - }, - "lint": { - "executor": "@nx/eslint:lint", - "outputs": ["{options.outputFile}"], - "options": { - "eslintConfig": "libs/auth/eslint.config.js" - } - } - } -} diff --git a/libs/auth/src/index.ts b/libs/auth/src/index.ts deleted file mode 100644 index 048d502d..00000000 --- a/libs/auth/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -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/i18n/i18n.ts b/libs/auth/src/lib/i18n/i18n.ts deleted file mode 100644 index a54c7b0c..00000000 --- a/libs/auth/src/lib/i18n/i18n.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TranslationsStruct } from '@asset-sg/client-shared'; - -export const deAuthTranslations = { - auth2: { - reset: { - unauthorised: 'E-Mail-Link ist ungültig oder abgelaufen', - }, - }, -}; - -export const enAuthTranslations: AuthTranslations = { - auth2: { - reset: { - unauthorised: 'Email link is invalid or expired', - }, - }, -}; - -export const frAuthTranslations: AuthTranslations = { - auth2: { - reset: { - unauthorised: 'FR E-Mail-Link ist ungültig oder abgelaufen', - }, - }, -}; - -export const itAuthTranslations: AuthTranslations = { - auth2: { - reset: { - unauthorised: 'IT E-Mail-Link ist ungültig oder abgelaufen', - }, - }, -}; - -export const rmAuthTranslations: AuthTranslations = { - auth2: { - reset: { - unauthorised: 'RM E-Mail-Link ist ungültig oder abgelaufen', - }, - }, -}; - -export type AuthTranslations = typeof deAuthTranslations; - -export const authTranslations: TranslationsStruct = { - de: deAuthTranslations, - en: enAuthTranslations, - fr: frAuthTranslations, - it: itAuthTranslations, - rm: rmAuthTranslations, -}; diff --git a/libs/auth/src/lib/i18n/index.ts b/libs/auth/src/lib/i18n/index.ts deleted file mode 100644 index e82230f1..00000000 --- a/libs/auth/src/lib/i18n/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './i18n'; diff --git a/libs/auth/src/lib/models/error-payload.ts b/libs/auth/src/lib/models/error-payload.ts deleted file mode 100644 index 77e7e255..00000000 --- a/libs/auth/src/lib/models/error-payload.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as D from 'io-ts/Decoder'; - -export const AuthErrorPayload = D.struct({ - code: D.number, - msg: D.string, -}); -export type AuthErrorPayload = D.TypeOf; diff --git a/libs/auth/src/lib/models/hash-params.ts b/libs/auth/src/lib/models/hash-params.ts deleted file mode 100644 index 9bf41286..00000000 --- a/libs/auth/src/lib/models/hash-params.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { pipe } from 'fp-ts/function'; -import * as D from 'io-ts/Decoder'; -import queryString from 'query-string'; - -export const passwordHashParams = (type: 'invite' | 'recovery') => - pipe( - D.string, - D.parse((hash: string) => - !hash || hash[0] !== '#' ? D.failure(hash, 'HashParams') : D.success(queryString.parse(hash.slice(1))) - ), - D.compose( - D.union( - pipe( - D.struct({ - type: D.literal(type), - access_token: D.string, - }), - D.map((a) => ({ ...a, hashParamsStatus: 'success' as const })) - ), - pipe( - D.struct({ error: D.literal('unauthorized_client'), error_code: D.literal('401') }), - D.map((a) => ({ ...a, hashParamsStatus: 'failure' as const })) - ) - ) - ) - ); - -export type PasswordHashParams = D.TypeOf>; diff --git a/libs/auth/src/lib/models/index.ts b/libs/auth/src/lib/models/index.ts deleted file mode 100644 index 71d933a5..00000000 --- a/libs/auth/src/lib/models/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './error-payload'; -export * from './hash-params'; diff --git a/libs/auth/src/lib/styles/_mixins.scss b/libs/auth/src/lib/styles/_mixins.scss deleted file mode 100644 index 0e54382e..00000000 --- a/libs/auth/src/lib/styles/_mixins.scss +++ /dev/null @@ -1 +0,0 @@ -@import "../../../../../libs/client-shared/src/lib/styles/mixins"; diff --git a/libs/auth/src/lib/styles/_variables.scss b/libs/auth/src/lib/styles/_variables.scss deleted file mode 100644 index 7383a2d3..00000000 --- a/libs/auth/src/lib/styles/_variables.scss +++ /dev/null @@ -1 +0,0 @@ -@import "../../../../../libs/client-shared/src/lib/styles/variables"; diff --git a/libs/auth/src/test-setup.ts b/libs/auth/src/test-setup.ts deleted file mode 100644 index 1100b3e8..00000000 --- a/libs/auth/src/test-setup.ts +++ /dev/null @@ -1 +0,0 @@ -import 'jest-preset-angular/setup-jest'; diff --git a/libs/auth/tsconfig.json b/libs/auth/tsconfig.json deleted file mode 100644 index 92049739..00000000 --- a/libs/auth/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "compilerOptions": { - "target": "es2022", - "useDefineForClassFields": false, - "forceConsistentCasingInFileNames": true, - "strict": true, - "noImplicitOverride": true, - "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": true - }, - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" - } - ], - "extends": "../../tsconfig.base.json", - "angularCompilerOptions": { - "enableI18nLegacyMessageIdFormat": false, - "strictInjectionParameters": true, - "strictInputAccessModifiers": true, - "strictTemplates": true - } -} diff --git a/libs/auth/tsconfig.lib.json b/libs/auth/tsconfig.lib.json deleted file mode 100644 index dcc35a71..00000000 --- a/libs/auth/tsconfig.lib.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "declaration": true, - "declarationMap": true, - "inlineSources": true, - "types": [] - }, - "exclude": ["src/test-setup.ts", "src/**/*.spec.ts", "jest.config.ts", "src/**/*.test.ts"], - "include": ["src/**/*.ts"] -} diff --git a/libs/auth/tsconfig.lib.prod.json b/libs/auth/tsconfig.lib.prod.json deleted file mode 100644 index 61b52378..00000000 --- a/libs/auth/tsconfig.lib.prod.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.lib.json", - "compilerOptions": { - "declarationMap": false - }, - "angularCompilerOptions": {} -} diff --git a/libs/auth/tsconfig.spec.json b/libs/auth/tsconfig.spec.json deleted file mode 100644 index 9e1c0c77..00000000 --- a/libs/auth/tsconfig.spec.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] - }, - "files": ["src/test-setup.ts"], - "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] -} diff --git a/libs/client-shared/src/lib/components/index.ts b/libs/client-shared/src/lib/components/index.ts index 570ec743..6b5f5239 100644 --- a/libs/client-shared/src/lib/components/index.ts +++ b/libs/client-shared/src/lib/components/index.ts @@ -8,4 +8,5 @@ export * from './ol-zoom-controls'; export * from './value-item'; export * from './view-child-marker'; export * from './smart-translate.pipe'; +export * from './language-selector'; export * from './confirm-dialog'; diff --git a/libs/client-shared/src/lib/components/language-selector/index.ts b/libs/client-shared/src/lib/components/language-selector/index.ts new file mode 100644 index 00000000..460e5d9b --- /dev/null +++ b/libs/client-shared/src/lib/components/language-selector/index.ts @@ -0,0 +1 @@ +export * from './language-selector.component'; diff --git a/libs/client-shared/src/lib/components/language-selector/language-selector.component.html b/libs/client-shared/src/lib/components/language-selector/language-selector.component.html new file mode 100644 index 00000000..aa97bf16 --- /dev/null +++ b/libs/client-shared/src/lib/components/language-selector/language-selector.component.html @@ -0,0 +1,25 @@ + + + + @for (language of languages; track language.lang) { + + + + + + {{ language.lang }} + + + } + + diff --git a/libs/client-shared/src/lib/components/language-selector/language-selector.component.scss b/libs/client-shared/src/lib/components/language-selector/language-selector.component.scss new file mode 100644 index 00000000..10aaea88 --- /dev/null +++ b/libs/client-shared/src/lib/components/language-selector/language-selector.component.scss @@ -0,0 +1,87 @@ +button.language { + text-transform: uppercase; + width: 69px; + height: 36px; + padding: 8px 12px; + border-radius: 4px; + color: #337083; + transition: 150ms ease-in; + transition-property: color, background-color; + + & > ::ng-deep .mdc-button__label { + width: 100%; + height: 100%; + + display: flex; + justify-content: space-between; + align-items: center; + padding: 0; + } + + &:hover, + &[aria-expanded="true"] { + background-color: #adc6cd; + color: #2f4356; + } + + svg-icon { + display: flex; + align-items: center; + width: 20px; + height: 20px; + } + + svg-icon.close { + display: none; + } + + &[aria-expanded="true"] svg-icon { + &.open { + display: none; + } + + &.close { + display: flex; + } + } +} + +::ng-deep .languages + * .mat-mdc-menu-panel { + text-transform: uppercase; + min-width: 85px; + max-width: 85px; + + & > .mat-mdc-menu-content { + display: flex; + flex-direction: column; + padding: 0; + } + + a { + color: #1c2834; + text-decoration: none; + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + transition: color 150ms ease-in; + } + + a:hover { + background-color: #dfe4e9; + } + + a > span.name { + flex-basis: 30%; + } + + a > span.icon { + flex-grow: 1; + display: flex; + justify-content: flex-start; + } + + a:not(.is-active) > span.icon { + visibility: hidden; + } +} diff --git a/libs/client-shared/src/lib/components/language-selector/language-selector.component.ts b/libs/client-shared/src/lib/components/language-selector/language-selector.component.ts new file mode 100644 index 00000000..328b15a3 --- /dev/null +++ b/libs/client-shared/src/lib/components/language-selector/language-selector.component.ts @@ -0,0 +1,60 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { MatButton } from '@angular/material/button'; +import { MatMenu, MatMenuTrigger } from '@angular/material/menu'; +import { NavigationEnd, Router, RouterLink } from '@angular/router'; +import { isNotNull, isTruthy } from '@asset-sg/core'; +import { Lang } from '@asset-sg/shared'; +import { SvgIconComponent } from '@ngneat/svg-icon'; +import { LetModule } from '@rx-angular/template/let'; +import { flow, pipe } from 'fp-ts/function'; +import * as O from 'fp-ts/Option'; +import queryString from 'query-string'; +import { debounceTime, filter, map, startWith } from 'rxjs'; +import { supportedLangs } from '../../i18n'; +import { AnchorComponent } from '../button'; + +@Component({ + selector: 'asset-sg-language-selector', + standalone: true, + templateUrl: './language-selector.component.html', + styleUrls: ['./language-selector.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SvgIconComponent, MatMenu, MatMenuTrigger, RouterLink, MatButton, AnchorComponent, LetModule], +}) +export class LanguageSelectorComponent { + private readonly router = inject(Router); + + public readonly currentLang$ = this.router.events.pipe( + filter((e): e is NavigationEnd => e instanceof NavigationEnd), + map((e) => e.urlAfterRedirects), + startWith(this.router.url), + map( + flow( + (url) => O.of(url.match('^/(\\w\\w)(.*)$')), + O.filter(isTruthy), + O.bindTo('match'), + O.bind('lang', ({ match }) => pipe(Lang.decode(match[1]), O.fromEither)), + O.bind('parsed', ({ match }) => O.of(queryString.parseUrl(match[2]))), + O.map(({ parsed, lang }) => ({ + lang, + path: parsed.url, + queryParams: parsed.query, + })) + ) + ), + map((it) => O.toNullable(it)), + filter(isNotNull) + ); + + public readonly languages$ = this.currentLang$.pipe( + debounceTime(0), + map((currentLang) => + supportedLangs.map((lang) => ({ + isActive: lang === currentLang.lang, + lang: lang.toUpperCase(), + params: [`/${lang}${currentLang.path}`], + queryParams: currentLang.queryParams, + })) + ) + ); +} diff --git a/libs/client-shared/src/lib/utils/current-lang.ts b/libs/client-shared/src/lib/utils/current-lang.ts index ff7f3aaf..35fcf490 100644 --- a/libs/client-shared/src/lib/utils/current-lang.ts +++ b/libs/client-shared/src/lib/utils/current-lang.ts @@ -1,8 +1,8 @@ -import { InjectionToken, inject } from '@angular/core'; +import { inject, InjectionToken } from '@angular/core'; import { isNotNil } from '@asset-sg/core'; import { Lang } from '@asset-sg/shared'; import { TranslateService } from '@ngx-translate/core'; -import { Observable, filter, map, startWith } from 'rxjs'; +import { filter, map, Observable, startWith } from 'rxjs'; export const CURRENT_LANG = new InjectionToken>('@asset-sg/client-shared/current-lang'); diff --git a/tsconfig.base.json b/tsconfig.base.json index 6d1ce85c..a05295c2 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -10,7 +10,7 @@ "importHelpers": true, "target": "es2015", "module": "esnext", - "lib": ["es2020", "dom"], + "lib": ["es2021", "dom"], "skipLibCheck": true, "skipDefaultLibCheck": true, "allowSyntheticDefaultImports": true,