diff --git a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html b/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html
index 8c7f3a9e9f..9b0ad52ea3 100644
--- a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html
+++ b/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html
@@ -7,6 +7,9 @@
[class.collapsible]="section.collapsible"
*vdrIfPermissions="section.requiresPermission"
>
+
+
+
@@ -17,6 +20,13 @@
[routerLink]="getRouterLink(item)"
routerLinkActive="active"
>
+
+
+
{{ item.label | translate }}
diff --git a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss b/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss
index b1eb00f65f..21dbb3a94c 100644
--- a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss
+++ b/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.scss
@@ -1,4 +1,4 @@
-@import "variables";
+@import 'variables';
:host {
// flex: 0 0 auto;
@@ -14,3 +14,38 @@ nav.sidenav {
.nav-list clr-icon {
margin-right: 12px;
}
+
+.nav-group,
+.nav-link {
+ position: relative;
+}
+
+.status-badge {
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ border-radius: 50%;
+ border: 1px solid $color-grey-200;
+
+ &.info {
+ background-color: $color-primary-600;
+ }
+ &.success {
+ background-color: $color-success-500;
+ }
+ &.warning {
+ background-color: $color-warning-500;
+ }
+ &.error {
+ background-color: $color-error-400;
+ }
+}
+
+.nav-group .status-badge {
+ left: 10px;
+ top: 6px;
+}
+.nav-link .status-badge {
+ left: 25px;
+ top: 3px;
+}
diff --git a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.spec.ts b/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.spec.ts
deleted file mode 100644
index 46743d48dd..0000000000
--- a/packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.spec.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { MainNavComponent } from './main-nav.component';
-
-describe('MainNavComponent', () => {
- /*let component: MainNavComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ MainNavComponent ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(MainNavComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });*/
-});
diff --git a/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts b/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts
index dbc96826b2..e7e831bd3b 100644
--- a/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts
+++ b/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts
@@ -4,6 +4,22 @@ import { Observable } from 'rxjs';
import { DataService } from '../../data/providers/data.service';
import { NotificationService } from '../notification/notification.service';
+export type NavMenuBadgeType = 'none' | 'info' | 'success' | 'warning' | 'error';
+
+/**
+ * A color-coded notification badge which will be displayed by the
+ * NavMenuItem's icon.
+ */
+export interface NavMenuBadge {
+ type: NavMenuBadgeType;
+ /**
+ * If true, the badge will propagate to the NavMenuItem's
+ * parent section, displaying a notification badge next
+ * to the section name.
+ */
+ propagateToSection?: boolean;
+}
+
/**
* A NavMenuItem is a menu item in the main (left-hand side) nav
* bar.
@@ -15,6 +31,7 @@ export interface NavMenuItem {
onClick?: (event: MouseEvent) => void;
icon?: string;
requiresPermission?: string;
+ statusBadge?: Observable;
}
/**
diff --git a/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts b/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts
index 0ab11bd962..03af0bff6f 100644
--- a/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts
+++ b/packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder.service.ts
@@ -1,11 +1,18 @@
import { APP_INITIALIZER, Injectable, Provider } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
+import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { map, shareReplay } from 'rxjs/operators';
import { Permission } from '../../common/generated-types';
-import { ActionBarItem, NavMenuItem, NavMenuSection, RouterLinkDefinition } from './nav-builder-types';
+import {
+ ActionBarItem,
+ NavMenuBadgeType,
+ NavMenuItem,
+ NavMenuSection,
+ RouterLinkDefinition,
+} from './nav-builder-types';
/**
* @description
@@ -126,6 +133,7 @@ export function addActionBarItem(config: ActionBarItem): Provider {
export class NavBuilderService {
navMenuConfig$: Observable;
actionBarConfig$: Observable;
+ sectionBadges: { [sectionId: string]: Observable } = {};
private initialNavMenuConfig$ = new BehaviorSubject([]);
private addedNavMenuSections: Array<{ config: NavMenuSection; before?: string }> = [];
@@ -195,19 +203,19 @@ export class NavBuilderService {
const itemAdditions$ = of(this.addedNavMenuItems);
const combinedConfig$ = combineLatest(this.initialNavMenuConfig$, sectionAdditions$).pipe(
- map(([initalConfig, additions]) => {
+ map(([initialConfig, additions]) => {
for (const { config, before } of additions) {
if (!config.requiresPermission) {
config.requiresPermission = Permission.Authenticated;
}
- const index = initalConfig.findIndex(c => c.id === before);
+ const index = initialConfig.findIndex(c => c.id === before);
if (-1 < index) {
- initalConfig.splice(index, 0, config);
+ initialConfig.splice(index, 0, config);
} else {
- initalConfig.push(config);
+ initialConfig.push(config);
}
}
- return initalConfig;
+ return initialConfig;
}),
shareReplay(1),
);
@@ -219,9 +227,7 @@ export class NavBuilderService {
if (!section) {
// tslint:disable-next-line:no-console
console.error(
- `Could not add menu item "${item.config.id}", section "${
- item.sectionId
- }" does not exist`,
+ `Could not add menu item "${item.config.id}", section "${item.sectionId}" does not exist`,
);
} else {
const index = section.items.findIndex(i => i.id === item.before);
@@ -232,6 +238,32 @@ export class NavBuilderService {
}
}
}
+
+ // Aggregate any badges defined for the nav items in each section
+ for (const section of sections) {
+ const itemBadgeStatuses = section.items
+ .map(i => i.statusBadge)
+ .filter(notNullOrUndefined);
+ this.sectionBadges[section.id] = combineLatest(itemBadgeStatuses).pipe(
+ map(badges => {
+ const propagatingBadges = badges.filter(b => b.propagateToSection);
+ if (propagatingBadges.length === 0) {
+ return 'none';
+ }
+ const statuses = propagatingBadges.map(b => b.type);
+ if (statuses.includes('error')) {
+ return 'error';
+ } else if (statuses.includes('warning')) {
+ return 'warning';
+ } else if (statuses.includes('info')) {
+ return 'info';
+ } else {
+ return 'none';
+ }
+ }),
+ );
+ }
+
return sections;
}),
);