From 9d5434db98e6e10acd0c005e9356082fa6e930b2 Mon Sep 17 00:00:00 2001 From: xtep103 <302179003+xtep103@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:48:00 +0100 Subject: [PATCH] feat(notifications): implement real-time notification system with websocket and toast integration (#476) --- frontend/src/App.tsx | 126 ++++++++-------- .../components/agents/AgentDetailModal.tsx | 1 + .../agents/AgentReputationRadar.test.tsx | 2 +- frontend/src/components/agents/AgentTable.tsx | 19 +-- .../components/agents/TaskSubmissionForm.tsx | 40 ++--- frontend/src/components/common/DataTable.tsx | 34 ++++- .../src/components/common/FormField.test.tsx | 2 +- frontend/src/components/common/Toast.css | 8 - frontend/src/components/landing/Hero.tsx | 11 +- frontend/src/components/layout/TopNav.tsx | 35 +---- .../notifications/NotificationBell.test.tsx | 106 +++++++++++++ .../notifications/NotificationBell.tsx | 45 ++++++ .../notifications/NotificationCenter.css | 59 +++++++- .../notifications/NotificationCenter.test.tsx | 67 ++++++++- .../notifications/NotificationCenter.tsx | 121 +-------------- .../notifications/NotificationItem.tsx | 13 +- .../notifications/NotificationPanel.tsx | 139 ++++++++++++++++++ .../src/components/notifications/index.ts | 2 + .../src/components/wallet/SendXLMForm.tsx | 99 ++++++++----- .../components/wallet/TransactionTable.tsx | 22 --- frontend/src/context/NotificationContext.tsx | 104 ++++++++++--- frontend/src/context/ToastContext.tsx | 74 ++++------ frontend/src/hooks/useNotifications.ts | 3 +- frontend/src/pages/dashboard.tsx | 1 - frontend/src/styles/global.css | 6 +- frontend/src/types/notification.ts | 31 +++- 26 files changed, 754 insertions(+), 416 deletions(-) create mode 100644 frontend/src/components/notifications/NotificationBell.test.tsx create mode 100644 frontend/src/components/notifications/NotificationBell.tsx create mode 100644 frontend/src/components/notifications/NotificationPanel.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6bed989f..f6d89983 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,37 +17,11 @@ import RendererDemoPage from './pages/RendererDemoPage' import WalletPage from './pages/WalletPage' import DashboardPage from './pages/dashboard' import ErrorBoundary from './components/common/ErrorBoundary' -import { ProtectedRoute } from './components/common/ProtectedRoute' +import { ProtectedRoute } from './components/auth/ProtectedRoute' import { CommandPalette } from './components/common/CommandPalette' import { useCommandPalette } from './hooks/useCommandPalette' -import { ProtectedRoute } from './components/auth/ProtectedRoute' import './components/common/Toast.css' -/** - * Everything that needs router context lives here, so `` (mounted by - * `App` below) is already in place before `useCommandPalette` calls - * `useNavigate`. - */ -const AppContent: React.FC = () => { - return ( - - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - - ) -} - // Lives INSIDE : useCommandPalette() calls useNavigate(), which // throws the "may be used only in the context of a " invariant when // rendered above it. @@ -58,34 +32,67 @@ const RoutedContent: React.FC = () => { <> } /> - - - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - {import.meta.env.DEV && ( - } /> - )} - } /> - - - } /> + + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + {import.meta.env.DEV && ( + } /> + )} + } /> + + + } + /> } /> { onSearch={search} recentSearches={recentSearches} onRecentSearchClick={(query) => { - // Trigger search with the recent query search(query) }} /> @@ -109,9 +115,11 @@ const App: React.FC = () => { - - - + + + + + diff --git a/frontend/src/components/agents/AgentDetailModal.tsx b/frontend/src/components/agents/AgentDetailModal.tsx index 952679c6..5c0b87e4 100644 --- a/frontend/src/components/agents/AgentDetailModal.tsx +++ b/frontend/src/components/agents/AgentDetailModal.tsx @@ -17,6 +17,7 @@ interface AgentDetailModalProps { export function AgentDetailModal({ agent, onClose }: AgentDetailModalProps) { const { t } = useTranslation() + const { data: reputationData, loading: reputationLoading } = useAgentReputation(agent?.id || '') useEffect(() => { if (!agent) return diff --git a/frontend/src/components/agents/AgentReputationRadar.test.tsx b/frontend/src/components/agents/AgentReputationRadar.test.tsx index f2696827..7a94a3d1 100644 --- a/frontend/src/components/agents/AgentReputationRadar.test.tsx +++ b/frontend/src/components/agents/AgentReputationRadar.test.tsx @@ -8,7 +8,7 @@ vi.mock('recharts', async () => { return { ...OriginalRecharts, ResponsiveContainer: ({ children }: any) => ( -
{children}
+
{children}
), }; }); diff --git a/frontend/src/components/agents/AgentTable.tsx b/frontend/src/components/agents/AgentTable.tsx index eeaa1f22..8a207380 100644 --- a/frontend/src/components/agents/AgentTable.tsx +++ b/frontend/src/components/agents/AgentTable.tsx @@ -9,10 +9,10 @@ import styles from './AgentTable.module.css' interface AgentTableProps { agents: AgentRecord[] loading: boolean - sortKey: SortKey | null - sortDir: SortDir + sortKey?: SortKey | null + sortDir?: SortDir /** Toggle/apply sort on the given column. Reputation only sorts desc. */ - onSort: (key: SortKey) => void + onSort?: (key: SortKey) => void onRowClick: (agent: AgentRecord) => void } @@ -23,15 +23,6 @@ function truncateId(id: string): string { return `${id.slice(0, 6)}…${id.slice(-4)}` } -function SortIcon({ active, dir }: { active: boolean; dir: SortDir }) { - if (!active) return