Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dashboard/reports/wallet-integration.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"generatedAt": "2026-06-24T19:47:36.590Z",
"generatedAt": "2026-06-26T13:01:40.990Z",
"total": 3,
"passed": 3,
"failed": 0,
Expand Down
12 changes: 11 additions & 1 deletion dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { EventExplorerPage } from './pages/EventExplorerPage';
import { NotificationTimelineView } from './components/NotificationTimelineView';
import { ActivityFeed } from './components/ActivityFeed';
import { ExportHistoryPage } from './pages/ExportHistoryPage';
import { NotificationSearchPage } from './pages/NotificationSearchPage';

type Tab = 'explorer' | 'timeline' | 'activity' | 'export-history';
type Tab = 'explorer' | 'timeline' | 'activity' | 'export-history' | 'search';

export function App() {
const [tab, setTab] = useState<Tab>('explorer');
Expand Down Expand Up @@ -44,12 +45,21 @@ export function App() {
>
Export History
</button>
<button
role="tab"
aria-selected={tab === 'search'}
className={`app-tabs__btn${tab === 'search' ? ' app-tabs__btn--active' : ''}`}
onClick={() => setTab('search')}
>
Notification Search
</button>
</nav>

{tab === 'explorer' && <EventExplorerPage />}
{tab === 'timeline' && <NotificationTimelineView />}
{tab === 'activity' && <ActivityFeed />}
{tab === 'export-history' && <ExportHistoryPage />}
{tab === 'search' && <NotificationSearchPage />}
</div>
);
}
4 changes: 2 additions & 2 deletions dashboard/src/__tests__/wallet-integration.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ describe('Notification workflow with wallet connected', () => {
json: async () => ({
events: [
{
id: 'evt-wallet-1',
eventId: 'evt-wallet-1',
contractAddress: 'CTEST',
topic: 'task_created',
ledger: 100,
Expand All @@ -160,7 +160,7 @@ describe('Notification workflow with wallet connected', () => {

const events = await fetchEvents('http://localhost:8787/api/events');
expect(events).toHaveLength(1);
expect(events[0].id).toBe('evt-wallet-1');
expect(events[0].eventId).toBe('evt-wallet-1');
expect(fetchMock).toHaveBeenCalledWith('http://localhost:8787/api/events');
});
});
Expand Down
11 changes: 0 additions & 11 deletions dashboard/src/components/EventExplorerCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ interface EventExplorerCardProps {
export function EventExplorerCard({ event, onCopyContract, isCopied, contractStatuses }: EventExplorerCardProps) {
const contractStatus = contractStatuses.find(c => c.address === event.contractAddress);
const isPaused = contractStatus?.paused ?? false;
}

export function EventExplorerCard({ event, onCopyContract, isCopied }: EventExplorerCardProps) {
const label = event.eventName ?? event.type;
const badgeClass = getEventKindClass(event.type);
const kindLabel = getEventKindLabel(event.type);
Expand All @@ -67,14 +64,6 @@ export function EventExplorerCard({ event, onCopyContract, isCopied }: EventExpl
<span className="event-explorer__badge event-explorer__badge--paused">Paused</span>
)}
</div>
<button
type="button"
className="event-explorer__copy-button"
onClick={() => onCopyContract(event.contractAddress)}
aria-label={`Copy contract address ${event.contractAddress}`}
>
{isCopied ? 'Copied' : 'Copy'}
</button>
</div>
</div>

Expand Down
3 changes: 0 additions & 3 deletions dashboard/src/components/EventExplorerTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ interface EventExplorerTableProps {
}

export function EventExplorerTable({ events, contractStatuses }: EventExplorerTableProps) {
}

export function EventExplorerTable({ events }: EventExplorerTableProps) {
const [copiedAddress, setCopiedAddress] = useState<string | null>(null);

async function syncCopyText(text: string) {
Expand Down
200 changes: 200 additions & 0 deletions dashboard/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -1861,3 +1861,203 @@ body {
align-items: stretch;
}
}

/* ── Notification Search Page ──────────────────────────────────────────────── */

.notif-search-page {
max-width: 1100px;
margin: 0 auto;
padding: 24px;
}

.notif-search-page__header {
margin-bottom: 24px;
}

.notif-search-page__header h1 {
margin: 4px 0 8px;
font-size: 1.75rem;
}

.notif-search-form {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 12px;
padding: 16px;
margin-bottom: 24px;
}

.notif-search-form__row {
display: grid;
grid-template-columns: 2fr repeat(5, 1fr);
gap: 12px;
align-items: end;
}

.notif-search-form__group {
display: flex;
flex-direction: column;
gap: 6px;
}

.notif-search-form__group--wide {
grid-column: span 2;
}

.notif-search-form__label {
font-size: 0.82rem;
color: #9aa0a6;
font-weight: 500;
}

.notif-search-form__input {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.12);
border-radius: 8px;
color: #e8eaed;
font-size: 0.9rem;
padding: 8px 12px;
outline: none;
width: 100%;
}

.notif-search-form__input:focus {
border-color: #4a9eff;
}

.notif-search-page__status {
color: #9aa0a6;
padding: 8px 0;
}

.notif-search-page__empty {
text-align: center;
padding: 64px 24px;
color: #9aa0a6;
}

.notif-search-page__empty h2 {
margin: 0 0 8px;
font-size: 1.25rem;
color: #e8eaed;
}

.notif-search-page__empty p {
margin: 0;
}

.notif-search-results {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 16px;
}

.notif-result-card {
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 10px;
padding: 14px 16px;
}

.notif-result-card__header {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
}

.notif-result-card__source {
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}

.notif-result-card__source--scheduled { background: rgba(74,158,255,0.15); color: #4a9eff; }
.notif-result-card__source--processed { background: rgba(52,211,153,0.15); color: #34d399; }

.notif-result-card__status {
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
text-transform: uppercase;
}

.notif-result-card__status--completed,
.notif-result-card__status--processed { background: rgba(52,211,153,0.1); color: #34d399; }
.notif-result-card__status--pending { background: rgba(251,191,36,0.1); color: #fbbf24; }
.notif-result-card__status--failed { background: rgba(239,68,68,0.1); color: #ef4444; }
.notif-result-card__status--processing { background: rgba(74,158,255,0.1); color: #4a9eff; }
.notif-result-card__status--cancelled { background: rgba(156,163,175,0.1); color: #9ca3af; }

.notif-result-card__type {
font-size: 0.75rem;
color: #9aa0a6;
padding: 2px 8px;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 4px;
}

.notif-result-card__fields {
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 16px;
margin: 0;
font-size: 0.85rem;
}

.notif-result-card__fields dt {
color: #9aa0a6;
white-space: nowrap;
}

.notif-result-card__fields dd {
margin: 0;
color: #e8eaed;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.notif-result-card__fields code {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.8rem;
color: #a5d6ff;
}

.notif-search-page__pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-top: 24px;
}

.pagination__info {
color: #9aa0a6;
font-size: 0.9rem;
}

@media (max-width: 768px) {
.notif-search-form__row {
grid-template-columns: 1fr 1fr;
}

.notif-search-form__group--wide {
grid-column: span 2;
}

.notif-result-card__fields {
grid-template-columns: 1fr;
}

.notif-result-card__fields dt {
font-weight: 600;
margin-top: 6px;
}
}
Loading