diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts
index 2017dd898a..dba60dda81 100644
--- a/frontend/src/app/app.component.ts
+++ b/frontend/src/app/app.component.ts
@@ -57,9 +57,20 @@ export class AppComponent implements OnInit {
document.documentElement.style.setProperty('--vh', `${vh}px`);
}
- resizeMenu(type: 'COLLAPSE' | 'EXPAND' | 'NO_MARGIN') {
+ resizeMenu(type: 'COLLAPSE' | 'EXPAND' | 'NO_MARGIN' | 'HORIZONTAL') {
const progressFooter = document.getElementById('block-progress-footer');
switch (type) {
+ case 'HORIZONTAL': {
+ // Rail width is zeroed by the `layout-horizontal` root class, so the page
+ // only needs its left gutter removed; the top offset comes from --header-height.
+ document.body.style.setProperty('--header-width', '0px');
+ document.getElementById('main-content')!.style.left = '0';
+ document.getElementById('main-content')!.removeAttribute('main-collapse-menu');
+ if (progressFooter) {
+ progressFooter.style.paddingLeft = '48px';
+ }
+ break;
+ }
case 'COLLAPSE': {
document.body.style.setProperty('--header-width', 'var(--header-width-collapse)');
document.getElementById('main-content')!.style.left = 'var(--header-width-collapse)';
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 648edeb696..f9af9bf560 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -149,6 +149,7 @@ import { PolicyRepositoryService } from './services/policy-repository.service';
import { RelayerAccountsService } from './services/relayer-accounts.service';
import { RelayerAccountsComponent } from './views/relayer-accounts/relayer-accounts.component';
import { TreeTableModule } from 'primeng/treetable';
+import { MenubarModule } from 'primeng/menubar';
import { CredentialsPanelComponent } from './components/credentials/credentials-panel/credentials-panel.component';
const GuardianPreset = definePreset(Aura, {
@@ -290,7 +291,8 @@ const GuardianPreset = definePreset(Aura, {
CardModule,
ToggleSwitchModule,
AngularSvgIconModule.forRoot(),
- TreeTableModule
+ TreeTableModule,
+ MenubarModule
],
providers: [
WebSocketService,
diff --git a/frontend/src/app/components/notification/notification.component.html b/frontend/src/app/components/notification/notification.component.html
index ce1c2affcd..dfec6e7bd0 100644
--- a/frontend/src/app/components/notification/notification.component.html
+++ b/frontend/src/app/components/notification/notification.component.html
@@ -2,9 +2,10 @@
0"
(click)="onMenuOpened($event, notificationMenu)"
- class="badge"
+ [class.badge]="!compact"
+ [class.badge-dot]="compact"
>
-
+
{{ unreadNotifications }}
diff --git a/frontend/src/app/components/notification/notification.component.scss b/frontend/src/app/components/notification/notification.component.scss
index 1a8871dec3..2d00985c82 100644
--- a/frontend/src/app/components/notification/notification.component.scss
+++ b/frontend/src/app/components/notification/notification.component.scss
@@ -57,6 +57,18 @@
}
}
+.badge-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background-color: #ff432a;
+ cursor: pointer;
+
+ &:hover {
+ filter: brightness(1.2);
+ }
+}
+
.notification-position {
pointer-events: none;
position: fixed;
diff --git a/frontend/src/app/components/notification/notification.component.ts b/frontend/src/app/components/notification/notification.component.ts
index 88da8880a6..2407c2c4a4 100644
--- a/frontend/src/app/components/notification/notification.component.ts
+++ b/frontend/src/app/components/notification/notification.component.ts
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
+import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { NotificationType, NotifyAPI, } from '@guardian/interfaces';
import { ToastrService } from 'ngx-toastr';
@@ -19,6 +19,9 @@ export class NotificationComponent implements OnInit {
menuOpened: boolean = false;
subscription = new Subscription();
+ /** Show a plain red dot instead of the unread count (used in the collapsed menu). */
+ @Input() compact: boolean = false;
+
@Output() menuOpenedChange = new EventEmitter();
viewDetails($event: MouseEvent, notification: any) {
diff --git a/frontend/src/app/modules/policy-engine/policies/policies.component.scss b/frontend/src/app/modules/policy-engine/policies/policies.component.scss
index 2d31db4b6b..1c6f9a23e4 100644
--- a/frontend/src/app/modules/policy-engine/policies/policies.component.scss
+++ b/frontend/src/app/modules/policy-engine/policies/policies.component.scss
@@ -1154,7 +1154,7 @@
.options-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss
index 1769f57f08..7e30b3c5ef 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-reader-block/global-events-reader-block.component.scss
@@ -316,7 +316,7 @@
width: 100%;
border-top: 1px solid var(--color-grey-3, #E1E7EF);
background: var(--guardian-background, #FFF);
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
transition: padding-left 0.125s ease-in-out;
z-index: 999;
}
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss
index 058b6f8ef3..d0530e9548 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/global-events-writer-block/global-events-writer-block.component.scss
@@ -241,7 +241,7 @@
width: 100%;
border-top: 1px solid var(--color-grey-3, #E1E7EF);
background: var(--guardian-background, #FFF);
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
transition: padding-left 0.125s ease-in-out;
z-index: 999;
}
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
index 4a3f1259b0..98c1a4c47b 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/request-document-block/request-document-block.component.scss
@@ -179,7 +179,7 @@
width: 100%;
border-top: 1px solid var(--color-grey-3, #e1e7ef);
background: var(--guardian-background, #fff);
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
transition: padding-left 0.125s ease-in-out;
margin-top: 20px;
@@ -548,7 +548,7 @@
width: 100%;
border-top: 1px solid var(--color-grey-3, #e1e7ef);
background: var(--guardian-background, #fff);
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
transition: padding-left 0.125s ease-in-out;
margin-top: 20px;
left: 50%;
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss
index acf269bbda..073dd6f946 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/blocks/roles-block/roles-block.component.scss
@@ -131,7 +131,7 @@ form {
width: 100%;
border-top: 1px solid var(--color-grey-3, #E1E7EF);
background: var(--guardian-background, #FFF);
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
transition: padding-left 0.125s ease-in-out;
z-index: 999;
}
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss
index 464e4b1632..db376de266 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/policy-viewer/policy-viewer.component.scss
@@ -878,7 +878,7 @@ a.go-back-link svg {
.progress-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss b/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss
index 1064e3f2ca..3277e3a46e 100644
--- a/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss
+++ b/frontend/src/app/modules/policy-engine/policy-viewer/progress-tracker/progress-tracker.component.scss
@@ -15,7 +15,7 @@
.progress-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss b/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss
index 1ba74b3af8..e0a40aacfe 100644
--- a/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss
+++ b/frontend/src/app/modules/policy-engine/project-data-export/project-data-export.component.scss
@@ -462,7 +462,7 @@ a {
.progress-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/app/services/menu-layout.service.ts b/frontend/src/app/services/menu-layout.service.ts
new file mode 100644
index 0000000000..ee6e31f65c
--- /dev/null
+++ b/frontend/src/app/services/menu-layout.service.ts
@@ -0,0 +1,72 @@
+import { Injectable } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+
+export type MenuLayout = 'vertical' | 'horizontal';
+
+export interface MenuLayoutOption {
+ label: string;
+ value: MenuLayout;
+ icon: string;
+}
+
+const MENU_LAYOUT_STORAGE_KEY = 'MAIN_HEADER_LAYOUT';
+const HORIZONTAL_CLASS = 'layout-horizontal';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MenuLayoutService {
+ public readonly layouts: MenuLayoutOption[] = [
+ { label: 'Vertical', value: 'vertical', icon: 'pi-bars' },
+ { label: 'Horizontal', value: 'horizontal', icon: 'pi-window-maximize' }
+ ];
+
+ private readonly layout$ = new BehaviorSubject(this.readStoredLayout());
+
+ constructor() {
+ this.applyLayoutClass(this.layout$.value);
+ }
+
+ public get layout(): MenuLayout {
+ return this.layout$.value;
+ }
+
+ public get changes(): Observable {
+ return this.layout$.asObservable();
+ }
+
+ public setLayout(layout: MenuLayout): void {
+ const resolved = this.findLayout(layout).value;
+ if (resolved === this.layout$.value) {
+ return;
+ }
+ try {
+ localStorage.setItem(MENU_LAYOUT_STORAGE_KEY, resolved);
+ } catch (error) {
+ console.error(error);
+ }
+ this.applyLayoutClass(resolved);
+ this.layout$.next(resolved);
+ }
+
+ public toggle(): void {
+ this.setLayout(this.layout$.value === 'vertical' ? 'horizontal' : 'vertical');
+ }
+
+ private readStoredLayout(): MenuLayout {
+ try {
+ return this.findLayout(localStorage.getItem(MENU_LAYOUT_STORAGE_KEY)).value;
+ } catch (error) {
+ console.error(error);
+ return this.layouts[0].value;
+ }
+ }
+
+ private applyLayoutClass(layout: MenuLayout): void {
+ document.documentElement.classList.toggle(HORIZONTAL_CLASS, layout === 'horizontal');
+ }
+
+ private findLayout(layout: string | null): MenuLayoutOption {
+ return this.layouts.find((item) => item.value === layout) || this.layouts[0];
+ }
+}
diff --git a/frontend/src/app/utils/balance.ts b/frontend/src/app/utils/balance.ts
new file mode 100644
index 0000000000..3be48a6f06
--- /dev/null
+++ b/frontend/src/app/utils/balance.ts
@@ -0,0 +1,13 @@
+const HBAR_SYMBOL = 'ℏ';
+
+/** Standardise a balance for display: 3 decimals + ℏ. Non-numeric input is passed through. */
+export function formatBalance(balance: string | number | null | undefined): string {
+ if (balance === null || balance === undefined || balance === '') {
+ return '';
+ }
+ const value = typeof balance === 'number' ? balance : parseFloat(balance);
+ if (!isFinite(value)) {
+ return typeof balance === 'string' ? balance : '';
+ }
+ return `${value.toFixed(3)} ${HBAR_SYMBOL}`;
+}
diff --git a/frontend/src/app/utils/index.ts b/frontend/src/app/utils/index.ts
index 66812ac968..1c64601029 100644
--- a/frontend/src/app/utils/index.ts
+++ b/frontend/src/app/utils/index.ts
@@ -3,3 +3,5 @@ export { CategoryAccess, CategoryDetails, CategoryGroup } from "./permissions-ca
export { EntityAccess, EntityGroup } from "./permissions-entity";
export { PermissionsGroup } from "./permissions";
export { MergeUtils } from "./merge-utils";
+export { getUserInitials } from "./user-initials";
+export { formatBalance } from "./balance";
diff --git a/frontend/src/app/utils/user-initials.ts b/frontend/src/app/utils/user-initials.ts
new file mode 100644
index 0000000000..f32f283677
--- /dev/null
+++ b/frontend/src/app/utils/user-initials.ts
@@ -0,0 +1,14 @@
+/**
+ * Two-letter avatar initials: prefer the first two capital letters, otherwise the
+ * first two characters uppercased. Shared by the header avatar and the profile page.
+ */
+export function getUserInitials(username: string | null | undefined): string {
+ if (!username) {
+ return '?';
+ }
+ const caps = username.match(/[A-Z]/g);
+ if (caps && caps.length >= 2) {
+ return caps.slice(0, 2).join('');
+ }
+ return username.slice(0, 2).toUpperCase();
+}
diff --git a/frontend/src/app/views/admin/settings-view/settings-view.component.scss b/frontend/src/app/views/admin/settings-view/settings-view.component.scss
index d44d529d0d..7e855cbac8 100644
--- a/frontend/src/app/views/admin/settings-view/settings-view.component.scss
+++ b/frontend/src/app/views/admin/settings-view/settings-view.component.scss
@@ -42,13 +42,14 @@
.actions-container {
position: fixed;
- left: 270px;
+ left: var(--header-width, var(--header-width-expand));
right: 0;
bottom: 0;
height: 64px;
display: flex;
align-items: center;
justify-content: flex-end;
+ transition: left 0.125s ease-in-out;
background-color: var(--color-grey-white);
border-top: 1px solid var(--color-grey-3, #E1E7EF);
padding: 0 48px;
diff --git a/frontend/src/app/views/branding/branding.component.scss b/frontend/src/app/views/branding/branding.component.scss
index 5892a6fea4..ae9b4cdbc9 100644
--- a/frontend/src/app/views/branding/branding.component.scss
+++ b/frontend/src/app/views/branding/branding.component.scss
@@ -1,3 +1,13 @@
+.guardian-page {
+ // The shared .guardian-page is height: 100% + flex, so a plain padding-bottom
+ // can't reserve room for the fixed actions bar — overflowing content scrolls
+ // flush under it. Let the page grow with its content instead so the padding
+ // becomes real scroll space below the last card.
+ height: auto;
+ min-height: 100%;
+ padding-bottom: 88px;
+}
+
.not-exist {
position: absolute;
left: 48px;
@@ -55,13 +65,14 @@
.actions-container {
position: fixed;
- left: 270px;
+ left: var(--header-width, var(--header-width-expand));
right: 0;
bottom: 0;
height: 64px;
display: flex;
align-items: center;
justify-content: space-between;
+ transition: left 0.125s ease-in-out;
background-color: var(--color-grey-white);
border-top: 1px solid var(--color-grey-3, #E1E7EF);
padding: 0 48px;
diff --git a/frontend/src/app/views/new-header/menu.model.ts b/frontend/src/app/views/new-header/menu.model.ts
index 556602aa15..dd180a770a 100644
--- a/frontend/src/app/views/new-header/menu.model.ts
+++ b/frontend/src/app/views/new-header/menu.model.ts
@@ -3,8 +3,10 @@ import { UserPermissions, UserRole } from '@guardian/interfaces';
export interface NavbarMenuItem {
title: string;
childItems?: NavbarMenuItem[];
- iconUrl?: string;
- svgIcon?: string;
+ /** PrimeIcons class for the item, e.g. 'pi-wallet'. Inherits currentColor. */
+ icon?: string;
+ /** Optional uppercase group label rendered above this item (vertical layout). */
+ section?: string;
routerLink?: string;
active?: boolean;
allowedUserRoles?: UserRole[];
@@ -14,7 +16,8 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [
{
title: 'Policies',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
- iconUrl: 'table',
+ icon: 'pi-objects-column',
+ section: 'Workspace',
active: false,
childItems: [
{
@@ -53,7 +56,7 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [
},
{
title: 'Tokens',
- iconUrl: 'twoRings',
+ icon: 'pi-bitcoin',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
active: false,
childItems: [
@@ -69,7 +72,7 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [
},
{
title: 'Relayer Accounts',
- svgIcon: 'wallet',
+ icon: 'pi-wallet',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
active: false,
routerLink: '/relayer-accounts'
@@ -78,7 +81,8 @@ const NAVBAR_MENU_STANDARD_REGISTRY: NavbarMenuItem[] = [
title: 'Administration',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
active: false,
- iconUrl: 'stars',
+ icon: 'pi-sliders-h',
+ section: 'Administration',
childItems: [
{
title: 'Manage Roles',
@@ -117,12 +121,12 @@ const NAVBAR_MENU_AUDITOR: NavbarMenuItem[] = [
title: 'Audit',
allowedUserRoles: [UserRole.AUDITOR],
active: false,
- iconUrl: 'guard',
+ icon: 'pi-shield',
routerLink: '/audit'
},
{
title: 'Trust Chain',
- iconUrl: 'twoRings',
+ icon: 'pi-sitemap',
allowedUserRoles: [UserRole.AUDITOR],
active: false,
routerLink: '/trust-chain'
@@ -228,7 +232,8 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] {
menu.push({
title: 'Policies',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
- iconUrl: 'table',
+ icon: 'pi-objects-column',
+ section: 'Workspace',
active: false,
childItems
});
@@ -280,7 +285,7 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] {
}
menu.push({
title: 'Tokens',
- iconUrl: 'twoRings',
+ icon: 'pi-bitcoin',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
active: false,
childItems
@@ -346,7 +351,8 @@ function customMenu(user: UserPermissions): NavbarMenuItem[] {
title: 'Administration',
allowedUserRoles: [UserRole.STANDARD_REGISTRY],
active: false,
- iconUrl: 'stars',
+ icon: 'pi-sliders-h',
+ section: 'Administration',
childItems
});
}
diff --git a/frontend/src/app/views/new-header/new-header.component.html b/frontend/src/app/views/new-header/new-header.component.html
index f986c2d080..3741009303 100644
--- a/frontend/src/app/views/new-header/new-header.component.html
+++ b/frontend/src/app/views/new-header/new-header.component.html
@@ -1,196 +1,190 @@
@if (isLogin) {
-
-
-
-
- @if (menuItems) {
-
-
- }
+ @if (layout === 'horizontal') {
+
+
+
+
+

+
+
+
+
+
+
+
-
+ } @else {
+
+
+
+ @if (menuItems) {
+
+
+ }
+
+
+ }
}
-
-
-
-
-
-

-
+
+
+

+
+
+
+
+
-
-
- @for (barItem of barItems; track barItem) {
-
+ @for (barItem of menuItems; track barItem) {
+ @if (barItem.section && !menuCollapsed) {
+
+ }
+
- @if (barItem.iconUrl) {
-
![]()
- }
- @if (barItem.svgIcon) {
-
-
+ @if (barItem.icon) {
+
}
-
- {{ barItem.title }}
-
+
{{ barItem.title }}
@if (barItem.childItems) {
-

+
}
+
@if (!menuCollapsed && barItem.active && barItem.childItems) {
-
-
- }
-
- }
-
-
-
-
-
+
+
+ Menu layout
+ Navigation as a side rail or a top bar
+
+
+
+
+
diff --git a/frontend/src/app/views/root-profile/root-profile.component.ts b/frontend/src/app/views/root-profile/root-profile.component.ts
index 332221af99..a766498540 100644
--- a/frontend/src/app/views/root-profile/root-profile.component.ts
+++ b/frontend/src/app/views/root-profile/root-profile.component.ts
@@ -24,7 +24,9 @@ import { OtpDisableDialogComponent } from '../login/otp-disable-dialog/otp-disab
import moment from 'moment';
import { ToastrService } from 'ngx-toastr';
import { AppTheme, AppThemeOption, AppThemeService } from '../../services/app-theme.service';
+import { MenuLayout, MenuLayoutOption, MenuLayoutService } from '../../services/menu-layout.service';
import { DocWidgetService } from '../../services/doc-widget.service';
+import { formatBalance, getUserInitials } from '../../utils';
enum OperationMode {
None,
@@ -127,6 +129,7 @@ export class RootProfileComponent implements OnInit, OnDestroy {
private cdRef: ChangeDetectorRef,
private docWidgetService: DocWidgetService,
private appThemeService: AppThemeService,
+ private menuLayoutService: MenuLayoutService,
private toastr: ToastrService
) {
this.profile = null;
@@ -859,10 +862,11 @@ export class RootProfileComponent implements OnInit, OnDestroy {
}
getInitials(username: string | undefined): string {
- if (!username) { return '?'; }
- const caps = username.match(/[A-Z]/g);
- if (caps && caps.length >= 2) { return caps.slice(0, 2).join(''); }
- return username.slice(0, 2).toUpperCase();
+ return getUserInitials(username);
+ }
+
+ formatBalance(balance: string | null): string {
+ return formatBalance(balance);
}
formatRole(role: string | undefined): string {
@@ -925,6 +929,18 @@ export class RootProfileComponent implements OnInit, OnDestroy {
this.appThemeService.setTheme(theme);
}
+ get menuLayouts(): MenuLayoutOption[] {
+ return this.menuLayoutService.layouts;
+ }
+
+ get selectedMenuLayout(): MenuLayout {
+ return this.menuLayoutService.layout;
+ }
+
+ onMenuLayoutChange(layout: MenuLayout): void {
+ this.menuLayoutService.setLayout(layout);
+ }
+
onDocWidgetToggle(checked: boolean): void {
this.docWidgetService.setEnabled(checked);
}
diff --git a/frontend/src/app/views/schemas/schemas.component.scss b/frontend/src/app/views/schemas/schemas.component.scss
index 22a869afc1..62d0ea0e8a 100644
--- a/frontend/src/app/views/schemas/schemas.component.scss
+++ b/frontend/src/app/views/schemas/schemas.component.scss
@@ -722,7 +722,7 @@ a {
.options-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/app/views/token-config/token-config.component.scss b/frontend/src/app/views/token-config/token-config.component.scss
index 08afa917c3..311d686514 100644
--- a/frontend/src/app/views/token-config/token-config.component.scss
+++ b/frontend/src/app/views/token-config/token-config.component.scss
@@ -297,7 +297,7 @@
.options-footer {
padding: 12px 48px;
- padding-left: calc(var(--header-width-expand) + 48px);
+ padding-left: calc(var(--header-width, var(--header-width-expand)) + 48px);
display: flex;
justify-content: space-between;
position: fixed;
diff --git a/frontend/src/assets/images/icons/16/wallet.svg b/frontend/src/assets/images/icons/16/wallet.svg
deleted file mode 100644
index 19c9f2982a..0000000000
--- a/frontend/src/assets/images/icons/16/wallet.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/chevron-white.svg b/frontend/src/assets/images/icons/chevron-white.svg
deleted file mode 100644
index 6369dd839e..0000000000
--- a/frontend/src/assets/images/icons/chevron-white.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/guard.svg b/frontend/src/assets/images/icons/guard.svg
deleted file mode 100644
index d15d3687b4..0000000000
--- a/frontend/src/assets/images/icons/guard.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/notification.svg b/frontend/src/assets/images/icons/notification.svg
deleted file mode 100644
index 4ee93a0e71..0000000000
--- a/frontend/src/assets/images/icons/notification.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/stars.svg b/frontend/src/assets/images/icons/stars.svg
deleted file mode 100644
index 97dac4c33e..0000000000
--- a/frontend/src/assets/images/icons/stars.svg
+++ /dev/null
@@ -1,11 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/twoRings.svg b/frontend/src/assets/images/icons/twoRings.svg
deleted file mode 100644
index 535cc6e4e6..0000000000
--- a/frontend/src/assets/images/icons/twoRings.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
diff --git a/frontend/src/assets/images/icons/user.svg b/frontend/src/assets/images/icons/user.svg
deleted file mode 100644
index 733a0a5209..0000000000
--- a/frontend/src/assets/images/icons/user.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/frontend/src/variables.scss b/frontend/src/variables.scss
index 0b2f8c9a55..6b02338b22 100644
--- a/frontend/src/variables.scss
+++ b/frontend/src/variables.scss
@@ -45,6 +45,21 @@
--header-height-policy: 175px;
--header-background-color: #000;
+ /* Navigation menu (sidebar / top bar) — brand-tinted, light mode.
+ The look is driven entirely by these tokens; swap the ~9 values below for a
+ different palette (e.g. a neutral surface) without touching the template or
+ behavior. --sidebar-accent defaults to the brand primary, so customer branding
+ that overrides --color-primary automatically re-tints the active state. */
+ --sidebar-bg: #F4F7FE;
+ --sidebar-item-color: #3A4A73;
+ --sidebar-item-hover: #E6EDFB;
+ --sidebar-section-color: #7F88A8;
+ --sidebar-active-bg: var(--color-primary);
+ --sidebar-active-color: #FFFFFF;
+ --sidebar-accent: var(--color-primary);
+ --sidebar-border: #D9E4FB;
+ --sidebar-logo-color: #26215C;
+
--table-row-height: 64px;
--button-primary-color: #4169E2;
@@ -157,6 +172,26 @@
--formula-variable-background: #3A2E12;
--formula-function-background: #3A1730;
--formula-text-background: #12351F;
+
+ /* Navigation menu — brand-tinted, dark mode */
+ --sidebar-bg: #171B24;
+ --sidebar-item-color: #AAB4C5;
+ --sidebar-item-hover: #202735;
+ --sidebar-section-color: #778299;
+ --sidebar-active-bg: #1C2742;
+ --sidebar-active-color: #B5D4F4;
+ --sidebar-accent: #5D7FE8;
+ --sidebar-border: #303746;
+ --sidebar-logo-color: #FFFFFF;
+}
+
+/* Horizontal (top-bar) layout: zero the rail width so page content loses its left
+ gutter, and give the bar a height so content drops below it. Toggled on by
+ MenuLayoutService. */
+:root.layout-horizontal {
+ --header-width-expand: 0px;
+ --header-width-collapse: 0px;
+ --header-height: 56px;
}
@media (max-width: 810px) {