diff --git a/src/app/common/header/header.component.html b/src/app/common/header/header.component.html
index bac6b77c07..d33d403c62 100644
--- a/src/app/common/header/header.component.html
+++ b/src/app/common/header/header.component.html
@@ -34,6 +34,10 @@
+ @if (currentUser.role === 'Student') {
+
+ }
+
@if (currentUser.role === 'Admin' || currentUser.role === 'Convenor') {
+
+
+
+
+
+
+ {{ note.message }}
+
+
+
+
+
+
+ No notifications
+
+
+
+
0" class="delete-all-container">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/common/header/notifications-button/notifications-button.component.spec.ts b/src/app/common/header/notifications-button/notifications-button.component.spec.ts
new file mode 100644
index 0000000000..7dedd16e77
--- /dev/null
+++ b/src/app/common/header/notifications-button/notifications-button.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NotificationsButtonComponent } from './notifications-button.component';
+
+describe('NotificationsButtonComponent', () => {
+ let component: NotificationsButtonComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [NotificationsButtonComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(NotificationsButtonComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/common/header/notifications-button/notifications-button.component.ts b/src/app/common/header/notifications-button/notifications-button.component.ts
new file mode 100644
index 0000000000..3a0d1dbe9a
--- /dev/null
+++ b/src/app/common/header/notifications-button/notifications-button.component.ts
@@ -0,0 +1,93 @@
+import { Component, OnInit, OnDestroy, ElementRef, HostListener, ViewChild } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { AppInjector } from 'src/app/app-injector';
+import { DoubtfireConstants } from 'src/app/config/constants/doubtfire-constants';
+import { Subscription } from 'rxjs';
+
+// Define structure of a Notification object
+interface Notification {
+ id: number;
+ message: string;
+}
+
+@Component({
+ selector: 'f-notifications-button',
+ templateUrl: './notifications-button.component.html',
+ styleUrls: ['./notifications-button.component.css']
+})
+export class NotificationsButtonComponent implements OnInit, OnDestroy {
+
+ // Tracks whether the notifications dropdown is visible
+ showNotifications = false;
+
+ // List of notifications to be displayed
+ notifications: Notification[] = [];
+
+ // Track active subscriptions for cleanup
+ private subscriptions = new Subscription();
+
+ // Reference to the notification panel in the DOM
+ @ViewChild('panel') panelRef!: ElementRef;
+
+ private readonly API_URL = AppInjector.get(DoubtfireConstants).API_URL;
+
+ constructor(private http: HttpClient) {}
+
+ ngOnInit() {
+ this.loadNotifications();
+ }
+
+ ngOnDestroy() {
+ // Prevent memory leaks
+ this.subscriptions.unsubscribe();
+ }
+
+ // Close panel when clicking outside of it
+ @HostListener('document:click', ['$event'])
+ onDocumentClick(event: MouseEvent) {
+ if (this.showNotifications && this.panelRef && !this.panelRef.nativeElement.contains(event.target)) {
+ this.showNotifications = false;
+ }
+ }
+
+ // Toggle the visibility of the notifications dropdown
+ toggleNotifications() {
+ this.showNotifications = !this.showNotifications;
+ }
+
+ // Fetch notifications from the API
+ loadNotifications() {
+ this.subscriptions.add(
+ this.http.get(`${this.API_URL}/notifications`).subscribe({
+ next: data => this.notifications = data,
+ error: err => console.error('Error loading notifications', err)
+ })
+ );
+ }
+
+ // Remove a specific notification by ID
+ dismissNotification(notificationId: number) {
+ this.subscriptions.add(
+ this.http.delete(`${this.API_URL}/notifications/${notificationId}`).subscribe({
+ next: () => {
+ this.notifications = this.notifications.filter(note => note.id !== notificationId);
+ },
+ error: err => console.error('Error deleting notification', err)
+ })
+ );
+ }
+
+ // Delete all notifications
+ deleteAllNotifications() {
+ this.subscriptions.add(
+ this.http.delete(`${this.API_URL}/notifications`).subscribe({
+ next: () => {
+ this.notifications = [];
+ },
+ error: err => console.error('Error deleting all notifications', err)
+ })
+ );
+ }
+
+}
+
diff --git a/src/app/doubtfire-angular.module.ts b/src/app/doubtfire-angular.module.ts
index c714ff0e5a..e4c0dbad59 100644
--- a/src/app/doubtfire-angular.module.ts
+++ b/src/app/doubtfire-angular.module.ts
@@ -253,6 +253,7 @@ import {ScormExtensionModalComponent} from './common/modals/scorm-extension-moda
import { GradeIconComponent } from './common/grade-icon/grade-icon.component';
import { GradeTaskModalComponent } from './tasks/modals/grade-task-modal/grade-task-modal.component';
import { PrivacyPolicy } from './config/privacy-policy/privacy-policy';
+import {NotificationsButtonComponent} from './common/header/notifications-button/notifications-button.component';
// See https://stackoverflow.com/questions/55721254/how-to-change-mat-datepicker-date-format-to-dd-mm-yyyy-in-simplest-way/58189036#58189036
const MY_DATE_FORMAT = {
@@ -389,6 +390,7 @@ import { UnitStudentEnrolmentModalComponent } from './units/modals/unit-student-
TaskScormCardComponent,
ScormExtensionCommentComponent,
ScormExtensionModalComponent,
+ NotificationsButtonComponent,
],
// Services we provide
providers: [