diff --git a/apps/red-ui/src/app/app.module.ts b/apps/red-ui/src/app/app.module.ts
index f8e65170c..116926226 100644
--- a/apps/red-ui/src/app/app.module.ts
+++ b/apps/red-ui/src/app/app.module.ts
@@ -91,6 +91,7 @@ import { AceEditorModule } from 'ng2-ace-editor';
import { TeamMembersComponent } from './components/team-members/team-members.component';
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
+import { NotificationsComponent } from './components/notifications/notifications.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@@ -246,7 +247,8 @@ const matImports = [
DictionaryOverviewScreenComponent,
TeamMembersComponent,
AdminBreadcrumbsComponent,
- UserListingScreenComponent
+ UserListingScreenComponent,
+ NotificationsComponent
],
imports: [
BrowserModule,
diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html
index 4743f6ed0..bdad4ece7 100644
--- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html
+++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.html
@@ -6,8 +6,10 @@
[class.warn]="type === 'warn'"
[disabled]="disabled"
[class.small]="small"
+ [class.overlay]="showDot"
mat-icon-button
>
+
diff --git a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts
index ee2280b3c..085885e2a 100644
--- a/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts
+++ b/apps/red-ui/src/app/components/buttons/circle-button/circle-button.component.ts
@@ -8,6 +8,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
export class CircleButtonComponent implements OnInit {
@Input() icon: string;
@Input() tooltip: string;
+ @Input() showDot = false;
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
@Input() tooltipClass: string;
@Input() disabled = false;
diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.html b/apps/red-ui/src/app/components/notifications/notifications.component.html
new file mode 100644
index 000000000..844de2f90
--- /dev/null
+++ b/apps/red-ui/src/app/components/notifications/notifications.component.html
@@ -0,0 +1,24 @@
+
+
diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.scss b/apps/red-ui/src/app/components/notifications/notifications.component.scss
new file mode 100644
index 000000000..b34fb71a1
--- /dev/null
+++ b/apps/red-ui/src/app/components/notifications/notifications.component.scss
@@ -0,0 +1,73 @@
+@import '../../../assets/styles/red-variables';
+
+.mt-2 {
+ margin-top: 2px;
+}
+
+::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
+ right: 0 !important;
+
+ .notifications-menu {
+ min-width: 400px !important;
+ padding: 0 8px 16px;
+ max-height: calc(100vh - 200px);
+
+ .all-caps-label {
+ margin: 16px 0 6px 8px;
+ }
+
+ .mat-menu-item.notification {
+ padding: 8px 26px 10px 8px;
+ margin: 2px 0 0 0;
+ height: fit-content;
+ position: relative;
+ line-height: 18px;
+ align-items: flex-start;
+
+ .notification-content {
+ margin-top: 4px;
+ margin-left: 8px;
+ max-width: 320px;
+
+ > div {
+ white-space: normal;
+
+ a {
+ color: $accent;
+ font-weight: bold;
+ }
+ }
+ }
+
+ .dot {
+ height: 8px;
+ width: 8px;
+ border-radius: 50%;
+ background-color: $grey-4;
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ }
+
+ &:hover {
+ background-color: $grey-6;
+
+ .dot {
+ background-color: $grey-5;
+ }
+
+ a {
+ text-decoration: underline;
+ }
+ }
+
+ &.unread {
+ background-color: rgba($primary, 0.1);
+
+ .dot {
+ background-color: $primary;
+ }
+ }
+ }
+ }
+}
diff --git a/apps/red-ui/src/app/components/notifications/notifications.component.ts b/apps/red-ui/src/app/components/notifications/notifications.component.ts
new file mode 100644
index 000000000..d6c4b04bb
--- /dev/null
+++ b/apps/red-ui/src/app/components/notifications/notifications.component.ts
@@ -0,0 +1,73 @@
+import { Component, OnInit } from '@angular/core';
+import * as moment from 'moment';
+import { TranslateService } from '@ngx-translate/core';
+
+interface Notification {
+ message: string;
+ eventTime: number;
+ read: boolean;
+}
+
+@Component({
+ selector: 'redaction-notifications',
+ templateUrl: './notifications.component.html',
+ styleUrls: ['./notifications.component.scss']
+})
+export class NotificationsComponent implements OnInit {
+ public notifications: Notification[] = [
+ { message: 'This is a notification with longer text wrapping on multiple lines', eventTime: 1607340971000, read: false },
+ { message: 'This is a link', eventTime: 1607254981000, read: true },
+ { message: 'This is a notification 1', eventTime: 1607254571000, read: false },
+ { message: 'Notification', eventTime: 1607385727000, read: true },
+ { message: 'Another notification', eventTime: 1606829412000, read: false }
+ ];
+ public groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
+
+ constructor(private _translateService: TranslateService) {
+ this._groupNotifications();
+ }
+
+ ngOnInit(): void {}
+
+ public get hasUnread() {
+ return this.notifications.filter((notification) => !notification.read).length > 0;
+ }
+
+ private _groupNotifications() {
+ const res = {};
+ for (const notification of this.notifications) {
+ const date = moment(notification.eventTime).format('YYYY-MM-DD');
+ if (!res[date]) res[date] = [];
+ res[date].push(notification);
+ }
+ for (const key of Object.keys(res)) {
+ this.groupedNotifications.push({ dateString: key, notifications: res[key] });
+ }
+ }
+
+ public day(group: { dateString: string; notifications: Notification[] }): string {
+ moment.locale(this._translateService.currentLang);
+ return moment(group.notifications[0].eventTime).calendar({
+ sameDay: `[${this._translateService.instant('notifications.today')}]`,
+ lastDay: `[${this._translateService.instant('notifications.yesterday')}]`,
+ nextDay: `[${this._translateService.instant('notifications.tomorrow')}]`,
+ nextWeek: 'D MMMM',
+ lastWeek: 'D MMMM',
+ sameElse: 'D MMMM'
+ });
+ }
+
+ public eventTime(eventTime: number): string {
+ moment.locale(this._translateService.currentLang);
+ if (moment().isSame(eventTime, 'day')) {
+ return moment(eventTime).fromNow();
+ } else {
+ return moment(eventTime).format('hh:mm A');
+ }
+ }
+
+ public toggleRead(notification: Notification, $event) {
+ $event.stopPropagation();
+ notification.read = !notification.read;
+ }
+}
diff --git a/apps/red-ui/src/app/icons/icons.module.ts b/apps/red-ui/src/app/icons/icons.module.ts
index f8ef97199..8f3a5b189 100644
--- a/apps/red-ui/src/app/icons/icons.module.ts
+++ b/apps/red-ui/src/app/icons/icons.module.ts
@@ -41,6 +41,7 @@ export class IconsModule {
'logout',
'menu',
'needs-work',
+ 'notification',
'pages',
'plus',
'preview',
diff --git a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html
index 2071ed4f6..c26833d98 100644
--- a/apps/red-ui/src/app/screens/base-screen/base-screen.component.html
+++ b/apps/red-ui/src/app/screens/base-screen/base-screen.component.html
@@ -56,8 +56,9 @@