From cf4d622f0b610e834b116a78d27c490356d86d16 Mon Sep 17 00:00:00 2001 From: susanyusuf Date: Sat, 25 Jul 2026 10:07:34 +0100 Subject: [PATCH 1/2] feat: add loading skeletons for notification lists (#388) --- dashboard/src/App.tsx | 165 +++--------------- .../src/components/EventExplorerCard.tsx | 24 +-- .../src/components/EventExplorerTable.tsx | 11 +- .../src/components/EventsListSkeleton.tsx | 32 ++++ .../components/NotificationHealthPanel.tsx | 3 +- .../src/components/NotificationSearchBar.tsx | 4 +- .../components/NotificationSearchSkeleton.tsx | 50 ++++++ dashboard/src/index.css | 56 ++++++ dashboard/src/pages/EventExplorerPage.tsx | 7 +- dashboard/src/pages/EventsPage.test.tsx | 80 +++++++++ dashboard/src/pages/EventsPage.tsx | 10 +- .../src/pages/NotificationSearchPage.test.tsx | 100 +++++++++++ .../src/pages/NotificationSearchPage.tsx | 14 +- dashboard/src/pages/TemplatesPage.tsx | 17 +- dashboard/src/services/timelineApi.ts | 7 - dashboard/src/store/eventStore.test.tsx | 4 +- dashboard/src/store/eventStore.ts | 10 +- .../src/store/notificationStatusSync.test.tsx | 2 +- dashboard/src/types/event.ts | 5 +- dashboard/src/utils/eventData.ts | 2 +- 20 files changed, 402 insertions(+), 201 deletions(-) create mode 100644 dashboard/src/components/EventsListSkeleton.tsx create mode 100644 dashboard/src/components/NotificationSearchSkeleton.tsx create mode 100644 dashboard/src/pages/EventsPage.test.tsx create mode 100644 dashboard/src/pages/NotificationSearchPage.test.tsx diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index f4944682..f81f24c4 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,141 +1,20 @@ -import { useEffect, useMemo, useState } from 'react'; -import { EventExplorerPage } from './pages/EventExplorerPage'; -import { NotificationPreferencesPage } from './pages/NotificationPreferencesPage'; - -const VALID_TAB_KEYS = ['explorer', 'preferences'] as const; - -type TabKey = (typeof VALID_TAB_KEYS)[number]; - -function parseActiveTab(search: string): TabKey { - const params = new URLSearchParams(search); - const tab = params.get('tab'); - return VALID_TAB_KEYS.includes(tab as TabKey) ? (tab as TabKey) : 'explorer'; -} - -export function App() { - const initialSearch = typeof window !== 'undefined' ? window.location.search : ''; - const [activeTab, setActiveTab] = useState(() => parseActiveTab(initialSearch)); - - useEffect(() => { - if (typeof window === 'undefined') { - return; - } - - const handlePopState = () => { - setActiveTab(parseActiveTab(window.location.search)); - }; - - window.addEventListener('popstate', handlePopState); - return () => window.removeEventListener('popstate', handlePopState); - }, []); - - useEffect(() => { - if (typeof window === 'undefined') { - return; - } - - const params = new URLSearchParams(window.location.search); - params.set('tab', activeTab); - window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`); - }, [activeTab]); - - const tabLabel = useMemo( - () => (activeTab === 'preferences' ? 'Notification Preferences' : 'Event Explorer'), - [activeTab] - ); - - return ( -
-
-
-

Notify Chain

-

{tabLabel}

-
- - -
- - {activeTab === 'preferences' ? : } - className={`nav-tab-btn ${activeTab === 'exports' ? 'nav-tab-btn--active' : ''}`} - onClick={() => setActiveTab('exports')} - > - Export Center - -
- - - {activeTab === 'explorer' ? : } -import { DeliveryHeatmap } from './components/DeliveryHeatmap'; -import { ThemeToggle } from './components/ThemeToggle'; -import { useTheme } from './hooks/useTheme'; -import { useEventStore } from './store/eventStore'; - -export function App() { - const { theme, toggleTheme } = useTheme(); - const events = useEventStore((state) => state.events); - - return ( -
-
- -
- - -import { TemplatePreviewDemoPage } from './pages/TemplatePreviewDemoPage'; - -type Page = 'events' | 'templates'; - -export function App() { - const [currentPage, setCurrentPage] = useState('templates'); - - return ( -
- - -
- {currentPage === 'events' && } - {currentPage === 'templates' && } -
role="tab" aria-selected={tab === 'timeline'} className={`app-tabs__btn${tab === 'timeline' ? ' app-tabs__btn--active' : ''}`} @@ -172,6 +41,7 @@ export function App() { Delivery Timeline + {tab === 'explorer' && } @@ -211,6 +93,7 @@ export function App() { {tab === 'webhooks' && } {tab === 'export-history' && } {tab === 'search' && } + {tab === 'preferences' && }
); } diff --git a/dashboard/src/components/EventExplorerCard.tsx b/dashboard/src/components/EventExplorerCard.tsx index cd905f68..c837c726 100644 --- a/dashboard/src/components/EventExplorerCard.tsx +++ b/dashboard/src/components/EventExplorerCard.tsx @@ -35,17 +35,15 @@ interface EventExplorerCardProps { onCopyContract: (contractAddress: string) => void; isCopied: boolean; onSelect?: (event: BlockchainEvent) => void; -} - -export function EventExplorerCard({ event, onCopyContract, isCopied, onSelect }: EventExplorerCardProps) { - contractStatuses: ContractStatus[]; + contractStatuses?: ContractStatus[]; } export function EventExplorerCard({ event, onCopyContract, isCopied, - contractStatuses, + onSelect, + contractStatuses = [], }: EventExplorerCardProps) { const contractStatus = contractStatuses.find((c) => c.address === event.contractAddress); const isPaused = contractStatus?.paused ?? false; @@ -77,22 +75,14 @@ export function EventExplorerCard({

{shortenAddress(event.contractAddress)}

-