+ {/* Category icon */}
+
+
+
+
+ {/* Content */}
+
+
+ {notification.title}
+
+
{notification.body}
+ {timeAgo && (
+
{timeAgo}
+ )}
+
+
+ {/* Unread dot */}
+ {!notification.read && (
+
+ )}
+
+ {/* Dismiss button (visible on hover) */}
+
+
+ );
+}
diff --git a/invofi/apps/frontend/src/components/notifications/NotificationPanel.tsx b/invofi/apps/frontend/src/components/notifications/NotificationPanel.tsx
new file mode 100644
index 000000000..3af3d7705
--- /dev/null
+++ b/invofi/apps/frontend/src/components/notifications/NotificationPanel.tsx
@@ -0,0 +1,188 @@
+'use client';
+
+// ── NotificationPanel (issue #255) ───────────────────────────────────────────
+// Slide-in panel that shows categorised notifications.
+// Uses existing Radix Tabs and the project's design system — no new deps.
+
+import { useRouter } from 'next/navigation';
+import { X, CheckCheck, Trash2, ChevronDown, BellOff } from 'lucide-react';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { NotificationItem } from './NotificationItem';
+import { useNotifications } from './NotificationProvider';
+import type { AppNotification, NotificationCategory } from '@/types';
+import { cn } from '@/lib/utils';
+
+interface NotificationPanelProps {
+ open: boolean;
+ onClose: () => void;
+}
+
+function EmptyState({ label }: { label: string }) {
+ return (
+
+ {/* Header */}
+
+
+
Notifications
+ {unreadCount > 0 && (
+
+ {unreadCount}
+
+ )}
+
+
+ {unreadCount > 0 && (
+
+ )}
+ {visibleNotifications.length > 0 && (
+
+ )}
+
+
+
+
+ {/* Tabs */}
+
+
+ {categories.map(({ value, label }) => (
+
+ {label}
+
+ ))}
+
+
+ {/* Scrollable body */}
+
+
+ {renderList(visibleNotifications)}
+
+
+ {categories.slice(1).map(({ value }) => (
+
+ {renderList(notificationsByCategory(value as NotificationCategory))}
+
+ ))}
+
+
+
+ {/* Load more */}
+ {hasMore && (
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/invofi/apps/frontend/src/components/notifications/NotificationPreferencesPanel.tsx b/invofi/apps/frontend/src/components/notifications/NotificationPreferencesPanel.tsx
new file mode 100644
index 000000000..86c11f663
--- /dev/null
+++ b/invofi/apps/frontend/src/components/notifications/NotificationPreferencesPanel.tsx
@@ -0,0 +1,162 @@
+'use client';
+
+// ── NotificationPreferencesPanel (issue #255) ─────────────────────────────────
+// Per-event-type opt-in toggles + browser notification permission toggle.
+// Rendered in the /settings page.
+
+import { useCallback, useEffect, useState } from 'react';
+import {
+ requestBrowserNotificationPermission,
+ getBrowserNotificationPermission,
+ isBrowserNotificationSupported,
+} from '@/lib/notifications/browserNotifications';
+import { useNotifications } from '@/components/notifications/NotificationProvider';
+import type { NotificationPreferences } from '@/types';
+
+interface ToggleRowProps {
+ id: string;
+ label: string;
+ description: string;
+ checked: boolean;
+ disabled?: boolean;
+ onChange: (checked: boolean) => void;
+}
+
+function ToggleRow({ id, label, description, checked, disabled = false, onChange }: ToggleRowProps) {
+ return (
+