diff --git a/.eslintrc.json b/.eslintrc.json
index 670fd062e..55573b760 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,12 +1,12 @@
{
"root": true,
"ignorePatterns": ["**/*"],
- "plugins": ["@nrwl/nx"],
+ "plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts"],
"rules": {
- "@nrwl/nx/enforce-module-boundaries": [
+ "@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
@@ -35,15 +35,14 @@
},
{
"files": ["*.ts"],
- "extends": ["plugin:@nrwl/nx/typescript"]
+ "extends": ["plugin:@nx/typescript"]
},
{
"files": ["*.ts"],
"extends": [
- "plugin:@nrwl/nx/angular",
+ "plugin:@nx/angular",
"eslint:recommended",
"plugin:@angular-eslint/recommended",
- "plugin:@angular-eslint/recommended--extra",
"plugin:@angular-eslint/template/process-inline-templates",
"plugin:prettier/recommended",
"plugin:rxjs/recommended"
@@ -80,7 +79,7 @@
"accessibility": "no-public"
}
],
- "@typescript-eslint/member-ordering": "error",
+ "@typescript-eslint/member-ordering": "warn",
"@typescript-eslint/naming-convention": [
"error",
{
@@ -183,10 +182,9 @@
},
{
"files": ["*.html"],
- "extends": ["plugin:@nrwl/nx/angular-template", "plugin:@angular-eslint/template/recommended"]
+ "extends": ["plugin:@nx/angular-template", "plugin:@angular-eslint/template/recommended"]
},
{
- // https://github.com/angular-eslint/angular-eslint#notes-for-eslint-plugin-prettier-users
"files": ["*.html"],
"excludedFiles": ["*inline-template-*.component.html"],
"extends": ["plugin:prettier/recommended"],
diff --git a/angular.json b/angular.json
deleted file mode 100644
index c05451e7d..000000000
--- a/angular.json
+++ /dev/null
@@ -1,144 +0,0 @@
-{
- "version": 1,
- "projects": {
- "red-domain": {
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "projectType": "library",
- "root": "libs/red-domain",
- "sourceRoot": "libs/red-domain/src",
- "prefix": "red"
- },
- "red-ui": {
- "$schema": "../../node_modules/nx/schemas/project-schema.json",
- "projectType": "application",
- "schematics": {
- "@schematics/angular:component": {
- "style": "scss",
- "skipTests": true
- },
- "@schematics/angular:class": {
- "skipTests": true
- },
- "@schematics/angular:directive": {
- "skipTests": true
- },
- "@schematics/angular:guard": {
- "skipTests": true
- },
- "@schematics/angular:pipe": {
- "skipTests": true
- },
- "@schematics/angular:service": {
- "skipTests": true
- }
- },
- "root": "apps/red-ui",
- "sourceRoot": "apps/red-ui/src",
- "prefix": "redaction",
- "architect": {
- "build": {
- "builder": "@angular-devkit/build-angular:browser",
- "options": {
- "outputPath": "dist/apps/red-ui",
- "index": "apps/red-ui/src/index.html",
- "main": "apps/red-ui/src/main.ts",
- "polyfills": "apps/red-ui/src/polyfills.ts",
- "tsConfig": "apps/red-ui/tsconfig.json",
- "baseHref": "/ui/",
- "assets": [
- "apps/red-ui/src/favicon.ico",
- {
- "glob": "**/*",
- "input": "node_modules/@pdftron/webviewer/public/",
- "output": "/assets/wv-resources/"
- },
- {
- "glob": "**/*",
- "input": "apps/red-ui/src/assets/",
- "output": "/assets/"
- },
- {
- "glob": "**/*",
- "input": "libs/common-ui/src/assets/",
- "output": "/assets/"
- },
- {
- "glob": "**/*",
- "input": "node_modules/monaco-editor",
- "output": "/assets/monaco-editor/"
- },
- "apps/red-ui/src/manifest.webmanifest"
- ],
- "styles": ["apps/red-ui/src/styles.scss", "libs/common-ui/src/assets/styles/common-styles.scss"],
- "stylePreprocessorOptions": {
- "includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
- },
- "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
- "vendorChunk": true,
- "extractLicenses": false,
- "buildOptimizer": false,
- "sourceMap": true,
- "optimization": false,
- "namedChunks": true
- },
- "configurations": {
- "production": {
- "fileReplacements": [
- {
- "replace": "apps/red-ui/src/environments/environment.ts",
- "with": "apps/red-ui/src/environments/environment.prod.ts"
- }
- ],
- "optimization": {
- "scripts": true,
- "styles": {
- "minify": true,
- "inlineCritical": false
- },
- "fonts": true
- },
- "outputHashing": "all",
- "sourceMap": false,
- "namedChunks": false,
- "extractLicenses": true,
- "vendorChunk": false,
- "buildOptimizer": true,
- "budgets": [
- {
- "type": "initial",
- "maximumWarning": "2mb",
- "maximumError": "5mb"
- },
- {
- "type": "anyComponentStyle",
- "maximumWarning": "6kb",
- "maximumError": "20kb"
- }
- ],
- "serviceWorker": true,
- "ngswConfigPath": "apps/red-ui/ngsw-config.json"
- }
- },
- "outputs": ["{options.outputPath}"]
- },
- "serve": {
- "builder": "@angular-devkit/build-angular:dev-server",
- "options": {
- "browserTarget": "red-ui:build"
- },
- "configurations": {
- "production": {
- "browserTarget": "red-ui:build:production"
- }
- }
- },
- "extract-i18n": {
- "builder": "@angular-devkit/build-angular:extract-i18n",
- "options": {
- "browserTarget": "red-ui:build"
- }
- }
- }
- }
- }
-}
diff --git a/apps/red-ui/project.json b/apps/red-ui/project.json
new file mode 100644
index 000000000..213866f34
--- /dev/null
+++ b/apps/red-ui/project.json
@@ -0,0 +1,132 @@
+{
+ "name": "red-ui",
+ "$schema": "../../node_modules/nx/schemas/project-schema.json",
+ "projectType": "application",
+ "generators": {
+ "@schematics/angular:component": {
+ "style": "scss",
+ "skipTests": true
+ },
+ "@schematics/angular:class": {
+ "skipTests": true
+ },
+ "@schematics/angular:directive": {
+ "skipTests": true
+ },
+ "@schematics/angular:guard": {
+ "skipTests": true
+ },
+ "@schematics/angular:pipe": {
+ "skipTests": true
+ },
+ "@schematics/angular:service": {
+ "skipTests": true
+ }
+ },
+ "sourceRoot": "apps/red-ui/src",
+ "prefix": "redaction",
+ "targets": {
+ "build": {
+ "executor": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/apps/red-ui",
+ "index": "apps/red-ui/src/index.html",
+ "main": "apps/red-ui/src/main.ts",
+ "polyfills": "apps/red-ui/src/polyfills.ts",
+ "tsConfig": "apps/red-ui/tsconfig.json",
+ "baseHref": "/ui/",
+ "assets": [
+ "apps/red-ui/src/favicon.ico",
+ {
+ "glob": "**/*",
+ "input": "node_modules/@pdftron/webviewer/public/",
+ "output": "/assets/wv-resources/"
+ },
+ {
+ "glob": "**/*",
+ "input": "apps/red-ui/src/assets/",
+ "output": "/assets/"
+ },
+ {
+ "glob": "**/*",
+ "input": "libs/common-ui/src/assets/",
+ "output": "/assets/"
+ },
+ {
+ "glob": "**/*",
+ "input": "node_modules/monaco-editor",
+ "output": "/assets/monaco-editor/"
+ },
+ "apps/red-ui/src/manifest.webmanifest"
+ ],
+ "styles": ["apps/red-ui/src/styles.scss", "libs/common-ui/src/assets/styles/common-styles.scss"],
+ "stylePreprocessorOptions": {
+ "includePaths": ["./apps/red-ui/src/assets/styles", "./libs/common-ui/src/assets/styles"]
+ },
+ "scripts": ["node_modules/@pdftron/webviewer/webviewer.min.js"],
+ "vendorChunk": true,
+ "extractLicenses": false,
+ "buildOptimizer": false,
+ "sourceMap": true,
+ "optimization": false,
+ "namedChunks": true
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "apps/red-ui/src/environments/environment.ts",
+ "with": "apps/red-ui/src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": {
+ "scripts": true,
+ "styles": {
+ "minify": true,
+ "inlineCritical": false
+ },
+ "fonts": true
+ },
+ "outputHashing": "all",
+ "sourceMap": false,
+ "namedChunks": false,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "2mb",
+ "maximumError": "5mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb",
+ "maximumError": "20kb"
+ }
+ ],
+ "serviceWorker": true,
+ "ngswConfigPath": "apps/red-ui/ngsw-config.json"
+ }
+ },
+ "outputs": ["{options.outputPath}"]
+ },
+ "serve": {
+ "executor": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "red-ui:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "red-ui:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "executor": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "red-ui:build"
+ }
+ }
+ }
+}
diff --git a/apps/red-ui/src/app/app-routing.module.ts b/apps/red-ui/src/app/app-routing.module.ts
index f39e3d8b4..d856101fd 100644
--- a/apps/red-ui/src/app/app-routing.module.ts
+++ b/apps/red-ui/src/app/app-routing.module.ts
@@ -8,7 +8,7 @@ import {
IqserAuthGuard,
IqserPermissionsGuard,
IqserRoutes,
- TenantResolveComponent,
+ TenantSelectComponent,
} from '@iqser/common-ui';
import { RedRoleGuard } from '@users/red-role.guard';
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
@@ -19,7 +19,7 @@ import { DossiersGuard } from '@guards/dossiers.guard';
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
import { FeaturesGuard } from '@guards/features-guard.service';
import { DossierTemplatesGuard } from '@guards/dossier-templates.guard';
-import { DossierTemplateExistsGuard } from '@guards/dossier-template-exists.guard';
+import { templateExistsWhenEnteringDossierList } from '@guards/dossier-template-exists.guard';
import { DashboardGuard } from '@guards/dashboard-guard.service';
import { TrashGuard } from '@guards/trash.guard';
import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE, FILE_ID } from '@red/domain';
@@ -180,9 +180,9 @@ const mainRoutes: IqserRoutes = [
{
path: `:${DOSSIER_TEMPLATE_ID}`,
children: dossierTemplateIdRoutes,
- canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
+ canActivate: [CompositeRouteGuard, IqserPermissionsGuard, templateExistsWhenEnteringDossierList()],
data: {
- routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
+ routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard],
permissions: {
allow: [
ROLES.any,
@@ -211,7 +211,7 @@ const routes: IqserRoutes = [
path: '',
pathMatch: 'full',
canActivate: [ifNotLoggedIn],
- component: TenantResolveComponent,
+ component: TenantSelectComponent,
},
{
path: ':tenant',
@@ -240,7 +240,7 @@ const routes: IqserRoutes = [
];
@NgModule({
- imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' })],
+ imports: [RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled', bindToComponentInputs: true })],
providers: [{ provide: RouteReuseStrategy, useExisting: CustomRouteReuseStrategy }],
exports: [RouterModule],
})
diff --git a/apps/red-ui/src/app/app.component.ts b/apps/red-ui/src/app/app.component.ts
index 35f63282f..ef9078aba 100644
--- a/apps/red-ui/src/app/app.component.ts
+++ b/apps/red-ui/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
+import { Component, inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
import { RouterHistoryService } from '@services/router-history.service';
import { DOCUMENT } from '@angular/common';
import { UserPreferenceService } from '@users/user-preference.service';
@@ -35,12 +35,11 @@ export class AppComponent implements OnDestroy {
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
private readonly _routerHistoryService: RouterHistoryService,
userPreferenceService: UserPreferenceService,
- @Inject(DOCUMENT) document: Document,
renderer: Renderer2,
private readonly _router: Router,
route: ActivatedRoute,
) {
- renderer.addClass(document.body, userPreferenceService.getTheme());
+ renderer.addClass(inject(DOCUMENT).body, userPreferenceService.getTheme());
loadCustomTheme();
const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams));
this.#subscription.add(sub);
diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts
index 6be71a305..aeff40854 100644
--- a/apps/red-ui/src/app/app.module.ts
+++ b/apps/red-ui/src/app/app.module.ts
@@ -67,6 +67,9 @@ import { RedRoleGuard } from '@users/red-role.guard';
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
+import { UserMenuComponent } from '@components/user-menu/user-menu.component';
+import { TenantsMenuComponent } from '@components/tenants-menu/tenants-menu.component';
+import { MatDividerModule } from '@angular/material/divider';
export const appModuleFactory = (config: AppConfig) => {
@NgModule({
@@ -81,6 +84,8 @@ export const appModuleFactory = (config: AppConfig) => {
SkeletonTopBarComponent,
SkeletonStatsComponent,
BaseScreenComponent,
+ UserMenuComponent,
+ TenantsMenuComponent,
DownloadsListScreenComponent,
],
imports: [
@@ -163,6 +168,7 @@ export const appModuleFactory = (config: AppConfig) => {
IqserListingModule,
IconButtonComponent,
TenantPipe,
+ MatDividerModule,
],
providers: [
{
diff --git a/apps/red-ui/src/app/components/auth-error/auth-error.component.html b/apps/red-ui/src/app/components/auth-error/auth-error.component.html
index 83eb55942..14f120f55 100644
--- a/apps/red-ui/src/app/components/auth-error/auth-error.component.html
+++ b/apps/red-ui/src/app/components/auth-error/auth-error.component.html
@@ -2,17 +2,17 @@
diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.html b/apps/red-ui/src/app/components/base-screen/base-screen.component.html
index ad8515fae..00385ac90 100644
--- a/apps/red-ui/src/app/components/base-screen/base-screen.component.html
+++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.html
@@ -21,8 +21,8 @@
@@ -36,18 +36,7 @@
-
+
diff --git a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts
index 27a80eb9b..983ac23bf 100644
--- a/apps/red-ui/src/app/components/base-screen/base-screen.component.ts
+++ b/apps/red-ui/src/app/components/base-screen/base-screen.component.ts
@@ -5,24 +5,14 @@ import { NavigationStart, Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
-import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { filter, map, startWith } from 'rxjs/operators';
-import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContextHolder, TenantsService } from '@iqser/common-ui';
+import { BASE_HREF, IqserPermissionsService, List, shareDistinctLast, TenantsService } from '@iqser/common-ui';
import { BreadcrumbsService } from '@services/breadcrumbs.service';
import { FeaturesService } from '@services/features.service';
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
import { ROLES } from '@users/roles';
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
-interface MenuItem {
- readonly id: string;
- readonly name: string;
- readonly routerLink?: string;
- readonly show: boolean;
- readonly action?: () => void;
- readonly showDot?: () => boolean;
-}
-
const isNavigationStart = event => event instanceof NavigationStart;
const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
@@ -32,74 +22,41 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
})
export class BaseScreenComponent {
readonly roles = ROLES;
- readonly tenantContext = inject(TenantsService);
readonly documentViewer = inject(REDDocumentViewer);
readonly currentUser = this.userService.currentUser;
- readonly userMenuItems: readonly MenuItem[] = [
- {
- id: 'account',
- name: _('top-bar.navigation-items.my-account.children.account'),
- routerLink: '/main/account',
- show: true,
- },
- {
- id: 'admin',
- name: _('top-bar.navigation-items.my-account.children.admin'),
- routerLink: '/main/admin',
- show: (this.currentUser.isManager || this.currentUser.isUserAdmin) && this.permissionsService.has([ROLES.templates.read]),
- },
- {
- id: 'downloads',
- name: _('top-bar.navigation-items.my-account.children.downloads'),
- routerLink: '/main/downloads',
- show: this.currentUser.isUser && this.permissionsService.has(ROLES.readDownloadStatus),
- },
- {
- id: 'trash',
- name: _('top-bar.navigation-items.my-account.children.trash'),
- routerLink: '/main/trash',
- show: this.currentUser.isUser && this.permissionsService.has([ROLES.dossiers.read, ROLES.files.readStatus]),
- },
- {
- id: 'select-tenant',
- name: _('top-bar.navigation-items.my-account.children.select-tenant'),
- action: () => this.selectTenant(),
- show: this.tenantContext.hasMultipleTenants,
- },
- ];
- readonly searchActions: readonly SpotlightSearchAction[] = [
+ readonly searchActions: List = [
{
text: this._translateService.instant('search.this-dossier'),
icon: 'red:enter',
- hide: (): boolean => this._hideSearchThisDossier,
- action: (query): void => this._searchThisDossier(query),
+ hide: (): boolean => this.#hideSearchThisDossier,
+ action: (query): void => this.#searchThisDossier(query),
},
{
text: this._translateService.instant('search.active-dossiers'),
icon: 'red:enter',
hide: () => !this._featuresService.isEnabled(DOSSIERS_ARCHIVE),
- action: (query): void => this._search(query, [], true),
+ action: (query): void => this.#search(query, [], true),
},
{
text: this._translateService.instant('search.all-dossiers'),
icon: 'red:enter',
- action: (query): void => this._search(query, []),
+ action: (query): void => this.#search(query, []),
},
];
- private readonly _baseHref = inject(BASE_HREF);
- private readonly _navigationStart$ = this._router.events.pipe(
+ readonly #baseHref = inject(BASE_HREF);
+ readonly #navigationStart$ = this._router.events.pipe(
filter(isNavigationStart),
map((event: NavigationStart) => event.url),
startWith(this._router.url),
shareDistinctLast(),
);
- readonly isSearchScreen$ = this._navigationStart$.pipe(map(isSearchScreen));
+ readonly isSearchScreen$ = this.#navigationStart$.pipe(map(isSearchScreen));
constructor(
private readonly _router: Router,
private readonly _translateService: TranslateService,
private readonly _featuresService: FeaturesService,
- private readonly _tenantContextHolder: TenantContextHolder,
+ protected readonly _tenantsService: TenantsService,
readonly permissionsService: IqserPermissionsService,
readonly userService: UserService,
readonly userPreferenceService: UserPreferenceService,
@@ -107,7 +64,7 @@ export class BaseScreenComponent {
readonly breadcrumbsService: BreadcrumbsService,
) {}
- private get _hideSearchThisDossier() {
+ get #hideSearchThisDossier() {
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
if (!routerLink) {
return true;
@@ -117,25 +74,17 @@ export class BaseScreenComponent {
return !isDossierOverview;
}
- trackByName(_index: number, item: MenuItem) {
- return item.name;
- }
-
- selectTenant() {
- window.open(window.location.origin + this._baseHref, '_blank');
- }
-
- private _search(query: string, dossierIds: string[], onlyActive = false) {
+ #search(query: string, dossierIds: string[], onlyActive = false) {
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
- this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/search`], { queryParams }).then();
+ this._router.navigate([`/${this._tenantsService.activeTenantId}/main/search`], { queryParams }).then();
}
- private _searchThisDossier(query: string) {
+ #searchThisDossier(query: string) {
const routerLink = this.breadcrumbsService.breadcrumbs[1]?.options?.routerLink;
if (!routerLink) {
- return this._search(query, []);
+ return this.#search(query, []);
}
const dossierId = routerLink[2];
- return this._search(query, [dossierId]);
+ return this.#search(query, [dossierId]);
}
}
diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.scss b/apps/red-ui/src/app/components/notifications/notifications.component.scss
index 23a290b31..d366b2d81 100644
--- a/apps/red-ui/src/app/components/notifications/notifications.component.scss
+++ b/apps/red-ui/src/app/components/notifications/notifications.component.scss
@@ -23,7 +23,7 @@
}
}
- .mat-menu-item.notification {
+ .mat-mdc-menu-item.notification {
padding: 8px 26px 10px 8px;
margin: 2px 0 0 0;
height: fit-content;
diff --git a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.scss b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.scss
index 1f1e8b80c..676d848ca 100644
--- a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.scss
+++ b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.scss
@@ -20,7 +20,7 @@
width: 300px;
}
-::ng-deep.search-menu .mat-menu-content {
+::ng-deep.search-menu .mat-mdc-menu-content {
padding: 8px !important;
}
diff --git a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.ts b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.ts
index 7b98158d9..76564aaa7 100644
--- a/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.ts
+++ b/apps/red-ui/src/app/components/spotlight-search/spotlight-search.component.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, HostListener, Input, ViewChild } fr
import { debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
-import { MatLegacyMenuTrigger as MatMenuTrigger } from '@angular/material/legacy-menu';
+import { MatMenuTrigger } from '@angular/material/menu';
@Component({
selector: 'redaction-spotlight-search [actions]',
diff --git a/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.html b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.html
new file mode 100644
index 000000000..1fcae0254
--- /dev/null
+++ b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.html
@@ -0,0 +1,14 @@
+
diff --git a/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.scss b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.scss
new file mode 100644
index 000000000..79680a493
--- /dev/null
+++ b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.scss
@@ -0,0 +1,13 @@
+:host {
+ display: contents;
+}
+
+.label {
+ font-family: var(--iqser-font-family);
+ font-size: var(--iqser-font-size);
+ background-color: var(--iqser-background);
+ color: var(--iqser-disabled);
+ padding-left: 17px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+}
diff --git a/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.ts b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.ts
new file mode 100644
index 000000000..6c080dc26
--- /dev/null
+++ b/apps/red-ui/src/app/components/tenants-menu/tenants-menu.component.ts
@@ -0,0 +1,29 @@
+import { Component, computed, inject } from '@angular/core';
+import { BASE_HREF, IStoredTenant, TenantsService } from '@iqser/common-ui';
+
+@Component({
+ selector: 'app-tenants-menu',
+ templateUrl: './tenants-menu.component.html',
+ styleUrls: ['./tenants-menu.component.scss'],
+})
+export class TenantsMenuComponent {
+ readonly tenantsService = inject(TenantsService);
+
+ readonly storedTenants = computed(() => this.#getStoredTenants(this.tenantsService.activeTenant().tenantId));
+ readonly #baseHref = inject(BASE_HREF);
+
+ trackBy(_index: number, item: IStoredTenant) {
+ return item.tenant.tenantId;
+ }
+
+ selectTenant(tenantId?: string) {
+ const tenant = tenantId ? '/' + tenantId : '/';
+ window.open(window.location.origin + this.#baseHref + tenant, '_blank');
+ }
+
+ #getStoredTenants(activeTenantId: string) {
+ const storedTenants = this.tenantsService.getStoredTenants();
+
+ return storedTenants.filter(t => t.tenant.tenantId !== activeTenantId);
+ }
+}
diff --git a/apps/red-ui/src/app/components/user-menu/user-menu.component.html b/apps/red-ui/src/app/components/user-menu/user-menu.component.html
new file mode 100644
index 000000000..a768c0b38
--- /dev/null
+++ b/apps/red-ui/src/app/components/user-menu/user-menu.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/apps/red-ui/src/app/components/user-menu/user-menu.component.scss b/apps/red-ui/src/app/components/user-menu/user-menu.component.scss
new file mode 100644
index 000000000..b7ca8fa44
--- /dev/null
+++ b/apps/red-ui/src/app/components/user-menu/user-menu.component.scss
@@ -0,0 +1,3 @@
+:host {
+ display: contents;
+}
diff --git a/apps/red-ui/src/app/components/user-menu/user-menu.component.ts b/apps/red-ui/src/app/components/user-menu/user-menu.component.ts
new file mode 100644
index 000000000..f0012aa5d
--- /dev/null
+++ b/apps/red-ui/src/app/components/user-menu/user-menu.component.ts
@@ -0,0 +1,56 @@
+import { Component, inject } from '@angular/core';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import { ROLES } from '@users/roles';
+import { getCurrentUser, IqserPermissionsService, List, TenantsService } from '@iqser/common-ui';
+import { User } from '@red/domain';
+import { UserService } from '@users/user.service';
+interface MenuItem {
+ readonly id: string;
+ readonly name: string;
+ readonly routerLink?: string;
+ readonly show: boolean;
+ readonly action?: () => void;
+ readonly showDot?: () => boolean;
+}
+
+@Component({
+ selector: 'app-user-menu',
+ templateUrl: './user-menu.component.html',
+ styleUrls: ['./user-menu.component.scss'],
+})
+export class UserMenuComponent {
+ readonly currentUser = getCurrentUser();
+ readonly tenantsService = inject(TenantsService);
+ readonly userService = inject(UserService);
+ readonly #permissionsService = inject(IqserPermissionsService);
+ readonly userMenuItems: List