diff --git a/dashboard/src/__tests__/SessionHistoryPage.test.tsx b/dashboard/src/__tests__/SessionHistoryPage.test.tsx index 728a4845..5f512126 100644 --- a/dashboard/src/__tests__/SessionHistoryPage.test.tsx +++ b/dashboard/src/__tests__/SessionHistoryPage.test.tsx @@ -53,7 +53,7 @@ describe('SessionHistoryPage', () => { render(); - await screen.findByText('No session history records found.'); + await screen.findByText('No session history records found'); fireEvent.change(screen.getByLabelText('Owner key ID'), { target: { value: 'owner-1' } }); fireEvent.change(screen.getByLabelText('Status'), { target: { value: 'active' } }); diff --git a/dashboard/src/components/shared/EmptyState.tsx b/dashboard/src/components/shared/EmptyState.tsx new file mode 100644 index 00000000..f2719451 --- /dev/null +++ b/dashboard/src/components/shared/EmptyState.tsx @@ -0,0 +1,40 @@ +/** + * components/shared/EmptyState.tsx — Reusable empty state with icon, title, description, optional CTA. + */ + +import type { ReactNode } from 'react'; + +interface EmptyStateProps { + icon: ReactNode; + title: string; + description?: string; + action?: { + label: string; + onClick: () => void; + }; + className?: string; +} + +export default function EmptyState({ icon, title, description, action, className = '' }: EmptyStateProps) { + return ( +
+
{icon}
+

{title}

+ {description && ( +

{description}

+ )} + {action && ( + + )} +
+ ); +} diff --git a/dashboard/src/pages/AuditPage.tsx b/dashboard/src/pages/AuditPage.tsx index 286c3a3f..700b706a 100644 --- a/dashboard/src/pages/AuditPage.tsx +++ b/dashboard/src/pages/AuditPage.tsx @@ -12,6 +12,7 @@ import { SearchX, AlertCircle, } from 'lucide-react'; +import EmptyState from '../components/shared/EmptyState'; import { fetchAuditLogs, type FetchAuditLogsParams } from '../api/client'; import type { AuditRecord } from '../types'; @@ -278,8 +279,11 @@ export default function AuditPage() { ) : records.length === 0 ? (
- -

No audit records found

+ } + title="No audit records found" + description="Audit logs will appear here when actions are performed." + />

{(appliedActor || appliedAction || appliedSessionId) ? 'Try adjusting your filters.' diff --git a/dashboard/src/pages/PipelinesPage.tsx b/dashboard/src/pages/PipelinesPage.tsx index cc95ec04..2238311f 100644 --- a/dashboard/src/pages/PipelinesPage.tsx +++ b/dashboard/src/pages/PipelinesPage.tsx @@ -4,7 +4,8 @@ import { useState, useEffect, useCallback } from 'react'; import { Link } from 'react-router-dom'; -import { Plus } from 'lucide-react'; +import { Plus, GitBranch } from 'lucide-react'; +import EmptyState from '../components/shared/EmptyState'; import { getPipelines } from '../api/client'; import type { PipelineInfo } from '../api/client'; import { useStore } from '../store/useStore'; @@ -133,7 +134,11 @@ export default function PipelinesPage() {

) : pipelines.length === 0 ? (
-

No pipelines yet

+ } + title="No pipelines yet" + description="Create a pipeline to automate session workflows." + />

Create a pipeline to run sessions in sequence

) : ( diff --git a/dashboard/src/pages/SessionHistoryPage.tsx b/dashboard/src/pages/SessionHistoryPage.tsx index 4879a360..2f9501f2 100644 --- a/dashboard/src/pages/SessionHistoryPage.tsx +++ b/dashboard/src/pages/SessionHistoryPage.tsx @@ -17,6 +17,7 @@ import { type SessionHistoryRecord, } from '../api/client'; import { formatTimeAgo } from '../utils/format'; +import EmptyState from '../components/shared/EmptyState'; const STATUS_OPTIONS = [ { value: '', label: 'All statuses' }, @@ -322,8 +323,11 @@ export default function SessionHistoryPage() { ) : records.length === 0 ? ( - - No session history records found. + } + title="No session history records found" + description="Try adjusting your filters or date range." + /> ) : ( diff --git a/dashboard/src/pages/UsersPage.tsx b/dashboard/src/pages/UsersPage.tsx index d3f511f1..ce0a412f 100644 --- a/dashboard/src/pages/UsersPage.tsx +++ b/dashboard/src/pages/UsersPage.tsx @@ -3,7 +3,8 @@ */ import { useCallback, useEffect, useMemo, useState } from 'react'; -import { AlertCircle, RefreshCw, UsersRound } from 'lucide-react'; +import { AlertCircle, RefreshCw, UsersRound, Users } from 'lucide-react'; +import EmptyState from '../components/shared/EmptyState'; import { fetchUsers, type UserSummary } from '../api/client'; import { formatTimeAgo } from '../utils/format'; @@ -155,7 +156,13 @@ export default function UsersPage() { ) : filtered.length === 0 ? ( - No users match the current filter. + + } + title="No users match the current filter" + description="Try adjusting your search or filter criteria." + /> + ) : ( filtered.map((user) => (