From f0d271720c959b38b01741d986d0d1af50978c3c Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 16 Jan 2026 08:16:05 +1000 Subject: [PATCH] feat(ui): add state components and apply design system compliance Add new reusable components: - EmptyState: for empty list/search states with icon, title, description - ErrorState: for error displays with optional retry button - LoadingState: for loading states with Spinner and message - PageHeader: for consistent page headers with title, description, actions Apply components across pages: - agents, collections, tool-search pages use new state components - stats page uses LoadingState and ErrorState - faq page uses Icon for chevrons - health page uses Table components - dashboard agent forms use Input, Select, Textarea, Label - Replace custom div spinners with Spinner component --- .../[username]/agents/[uid]/chat/page.tsx | 3 +- apps/web/src/app/agents/page.tsx | 51 +++---- apps/web/src/app/collections/page.tsx | 55 +++---- .../src/app/dashboard/agents/[id]/page.tsx | 5 +- .../web/src/app/dashboard/agents/new/page.tsx | 130 +++++++--------- apps/web/src/app/faq/page.tsx | 27 +--- apps/web/src/app/health/page.tsx | 140 ++++++++---------- apps/web/src/app/stats/page.tsx | 15 +- apps/web/src/app/tool/tool-search/page.tsx | 116 ++++++--------- packages/ui/package.json | 16 ++ packages/ui/src/EmptyState/EmptyState.tsx | 110 ++++++++++++++ packages/ui/src/ErrorState/ErrorState.tsx | 98 ++++++++++++ packages/ui/src/LoadingState/LoadingState.tsx | 81 ++++++++++ packages/ui/src/PageHeader/PageHeader.tsx | 86 +++++++++++ packages/ui/src/Table/Table.tsx | 2 +- 15 files changed, 607 insertions(+), 328 deletions(-) create mode 100644 packages/ui/src/EmptyState/EmptyState.tsx create mode 100644 packages/ui/src/ErrorState/ErrorState.tsx create mode 100644 packages/ui/src/LoadingState/LoadingState.tsx create mode 100644 packages/ui/src/PageHeader/PageHeader.tsx diff --git a/apps/web/src/app/(profile)/[username]/agents/[uid]/chat/page.tsx b/apps/web/src/app/(profile)/[username]/agents/[uid]/chat/page.tsx index d786658..6b32c2d 100644 --- a/apps/web/src/app/(profile)/[username]/agents/[uid]/chat/page.tsx +++ b/apps/web/src/app/(profile)/[username]/agents/[uid]/chat/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { LoadingState } from '@tpmjs/ui/LoadingState/LoadingState'; import { notFound, useParams, useRouter } from 'next/navigation'; import { useCallback, useEffect, useState } from 'react'; @@ -53,7 +54,7 @@ export default function PrettyChatPage(): React.ReactElement { if (isLoading) { return (
-
+
); } diff --git a/apps/web/src/app/agents/page.tsx b/apps/web/src/app/agents/page.tsx index ce54254..538e941 100644 --- a/apps/web/src/app/agents/page.tsx +++ b/apps/web/src/app/agents/page.tsx @@ -1,11 +1,13 @@ 'use client'; import { Badge } from '@tpmjs/ui/Badge/Badge'; -import { Button } from '@tpmjs/ui/Button/Button'; +import { EmptyState } from '@tpmjs/ui/EmptyState/EmptyState'; +import { ErrorState } from '@tpmjs/ui/ErrorState/ErrorState'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import { Input } from '@tpmjs/ui/Input/Input'; +import { LoadingState } from '@tpmjs/ui/LoadingState/LoadingState'; +import { PageHeader } from '@tpmjs/ui/PageHeader/PageHeader'; import { Select } from '@tpmjs/ui/Select/Select'; -import { Spinner } from '@tpmjs/ui/Spinner/Spinner'; import Link from 'next/link'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { TableVirtuoso } from 'react-virtuoso'; @@ -128,7 +130,7 @@ export default function PublicAgentsPage(): React.ReactElement { const TableHeader = useCallback( () => ( - + Name Description Provider @@ -148,7 +150,7 @@ export default function PublicAgentsPage(): React.ReactElement { {agent.name} @@ -219,13 +221,10 @@ export default function PublicAgentsPage(): React.ReactElement {
- {/* Header */} -
-

Public Agents

-

- Discover AI agents created and shared by the community -

-
+ {/* Filters */}
@@ -254,27 +253,15 @@ export default function PublicAgentsPage(): React.ReactElement { {/* Content */} {error ? ( -
- -

Error

-

{error}

- -
+ fetchAgents(0, true)} /> ) : isLoading ? ( -
- - Loading agents... -
+ ) : filteredAgents.length === 0 ? ( -
-
- -
-

No agents found

-

- {search ? 'Try adjusting your search terms' : 'Be the first to share a public agent!'} -

-
+ ) : ( <>
@@ -294,13 +281,13 @@ export default function PublicAgentsPage(): React.ReactElement { /> ), TableHead: (props) => ( - + ), TableBody: (props) => , TableRow: (props) => ( ), }} diff --git a/apps/web/src/app/collections/page.tsx b/apps/web/src/app/collections/page.tsx index af08633..24adb50 100644 --- a/apps/web/src/app/collections/page.tsx +++ b/apps/web/src/app/collections/page.tsx @@ -1,11 +1,13 @@ 'use client'; import { Badge } from '@tpmjs/ui/Badge/Badge'; -import { Button } from '@tpmjs/ui/Button/Button'; +import { EmptyState } from '@tpmjs/ui/EmptyState/EmptyState'; +import { ErrorState } from '@tpmjs/ui/ErrorState/ErrorState'; import { Icon } from '@tpmjs/ui/Icon/Icon'; import { Input } from '@tpmjs/ui/Input/Input'; +import { LoadingState } from '@tpmjs/ui/LoadingState/LoadingState'; +import { PageHeader } from '@tpmjs/ui/PageHeader/PageHeader'; import { Select } from '@tpmjs/ui/Select/Select'; -import { Spinner } from '@tpmjs/ui/Spinner/Spinner'; import Link from 'next/link'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { TableVirtuoso } from 'react-virtuoso'; @@ -122,7 +124,7 @@ export default function PublicCollectionsPage(): React.ReactElement { const TableHeader = useCallback( () => ( - + Name Description Tools @@ -140,7 +142,7 @@ export default function PublicCollectionsPage(): React.ReactElement { {collection.name} @@ -201,13 +203,10 @@ export default function PublicCollectionsPage(): React.ReactElement {
- {/* Header */} -
-

Public Collections

-

- Discover curated tool collections shared by the community -

-
+ {/* Filters */}
@@ -236,31 +235,15 @@ export default function PublicCollectionsPage(): React.ReactElement { {/* Content */} {error ? ( -
- -

Error

-

{error}

- -
+ fetchCollections(0, true)} /> ) : isLoading ? ( -
- - - Loading collections... - -
+ ) : filteredCollections.length === 0 ? ( -
-
- -
-

No collections found

-

- {search - ? 'Try adjusting your search terms' - : 'Be the first to share a public collection!'} -

-
+ ) : ( <>
@@ -280,13 +263,13 @@ export default function PublicCollectionsPage(): React.ReactElement { /> ), TableHead: (props) => ( - + ), TableBody: (props) => , TableRow: (props) => ( ), }} diff --git a/apps/web/src/app/dashboard/agents/[id]/page.tsx b/apps/web/src/app/dashboard/agents/[id]/page.tsx index 3efb612..aa97b96 100644 --- a/apps/web/src/app/dashboard/agents/[id]/page.tsx +++ b/apps/web/src/app/dashboard/agents/[id]/page.tsx @@ -7,6 +7,7 @@ import { Badge } from '@tpmjs/ui/Badge/Badge'; import { Button } from '@tpmjs/ui/Button/Button'; import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock'; import { Icon } from '@tpmjs/ui/Icon/Icon'; +import { Spinner } from '@tpmjs/ui/Spinner/Spinner'; import { Switch } from '@tpmjs/ui/Switch/Switch'; import { Table, @@ -819,7 +820,7 @@ export default function AgentDetailPage(): React.ReactElement { /> {isSearchingTools && (
-
+
)}
@@ -932,7 +933,7 @@ export default function AgentDetailPage(): React.ReactElement { /> {isSearchingCollections && (
-
+
)}
diff --git a/apps/web/src/app/dashboard/agents/new/page.tsx b/apps/web/src/app/dashboard/agents/new/page.tsx index 7e37d4c..2cfdc6f 100644 --- a/apps/web/src/app/dashboard/agents/new/page.tsx +++ b/apps/web/src/app/dashboard/agents/new/page.tsx @@ -4,6 +4,10 @@ import type { AIProvider } from '@tpmjs/types/agent'; import { PROVIDER_MODELS, SUPPORTED_PROVIDERS } from '@tpmjs/types/agent'; import { Button } from '@tpmjs/ui/Button/Button'; import { Icon } from '@tpmjs/ui/Icon/Icon'; +import { Input } from '@tpmjs/ui/Input/Input'; +import { Label } from '@tpmjs/ui/Label/Label'; +import { Select } from '@tpmjs/ui/Select/Select'; +import { Textarea } from '@tpmjs/ui/Textarea/Textarea'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -133,11 +137,10 @@ export default function NewAgentPage(): React.ReactElement {
- - + Name + +
-
-