From a3dcd053bc784585fd47edf6e4d8f1b15406a347 Mon Sep 17 00:00:00 2001 From: Sergey Kozyrenko Date: Thu, 16 Jul 2026 17:48:22 +0700 Subject: [PATCH] refactor(ui): replace custom StatusCard with the shadcn Empty primitive Consolidate every empty / loading / error / not-found / error-boundary state off the bespoke StatusCard onto the canonical shadcn Empty (already used by DataTable) and delete components/ui/status-card.tsx. Empty renders borderless and centered, so the wide-viewport "letterbox" look is fixed at the root instead of capping a solid card. Also in this pass: - DataTable: truncate column headers so plain-string headers in narrow fixed-size columns (e.g. "System Prompt") stop wrapping to two lines. - Settings > Providers / API Tokens: move the Create action into the page header (icon-collapses on mobile) and the API-token Playground/Swagger links into a header overflow menu; drop the cramped description row that needed the earlier truncate/flex-wrap workarounds. Those links use a relative href since the API base path is root-relative. - Settings > Prompts: drop the fixed size on the Name column of both tables so it flexes to fill (matching Providers) and truncates. - Settings > Account: keep "Member since" on one line at mobile widths (min-w-0 + truncate, badge shrink-0). Co-Authored-By: Claude Opus 4.8 --- .../shared/route-error-boundary.tsx | 37 +++-- frontend/src/components/ui/data-table.tsx | 2 +- frontend/src/components/ui/status-card.tsx | 25 ---- frontend/src/pages/flows/flows.tsx | 36 +++-- frontend/src/pages/knowledges/knowledges.tsx | 38 ++++-- .../src/pages/settings/settings-account.tsx | 6 +- .../pages/settings/settings-api-tokens.tsx | 127 ++++++++++-------- .../src/pages/settings/settings-prompt.tsx | 44 +++--- .../src/pages/settings/settings-prompts.tsx | 40 +++--- .../src/pages/settings/settings-provider.tsx | 30 +++-- .../src/pages/settings/settings-providers.tsx | 117 ++++++++-------- frontend/src/pages/templates/templates.tsx | 20 +-- 12 files changed, 281 insertions(+), 241 deletions(-) delete mode 100644 frontend/src/components/ui/status-card.tsx diff --git a/frontend/src/components/shared/route-error-boundary.tsx b/frontend/src/components/shared/route-error-boundary.tsx index 77dff7cb..75f3d50c 100644 --- a/frontend/src/components/shared/route-error-boundary.tsx +++ b/frontend/src/components/shared/route-error-boundary.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { useRouteError } from 'react-router-dom'; import { Button } from '@/components/ui/button'; -import { StatusCard } from '@/components/ui/status-card'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { isChunkLoadError, isDomDesyncError, reloadOnce } from '@/lib/chunk-reload'; /** @@ -29,30 +29,29 @@ function RouteErrorBoundary() { className="grid min-h-svh w-full place-items-center p-4" role="alert" > - + + + + + Something went wrong + + {isChunk + ? 'A new version was likely just deployed. Reloading will load the latest one.' + : isDesync + ? 'The page hit a display glitch. Reloading usually clears it.' + : 'The page ran into an unexpected error. Reloading usually clears it.'} + + + - } - description={ - isChunk - ? 'A new version was likely just deployed. Reloading will load the latest one.' - : isDesync - ? 'The page hit a display glitch. Reloading usually clears it.' - : 'The page ran into an unexpected error. Reloading usually clears it.' - } - icon={ - - } - title="Something went wrong" - /> + + ); } diff --git a/frontend/src/components/ui/data-table.tsx b/frontend/src/components/ui/data-table.tsx index 043cc5c6..6d99928b 100644 --- a/frontend/src/components/ui/data-table.tsx +++ b/frontend/src/components/ui/data-table.tsx @@ -764,7 +764,7 @@ function DataTable({ > {headerGroup.headers.map((header) => ( - - {icon &&
{icon}
} -

{title}

- {description &&
{description}
} - {action &&
{action}
} -
- - ); -} diff --git a/frontend/src/pages/flows/flows.tsx b/frontend/src/pages/flows/flows.tsx index 48ba01f0..6823ad29 100644 --- a/frontend/src/pages/flows/flows.tsx +++ b/frontend/src/pages/flows/flows.tsx @@ -29,7 +29,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { StatusCard } from '@/components/ui/status-card'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { Toggle } from '@/components/ui/toggle'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { RenameFlowDocument, ResultType, StatusType, type TerminalFragmentFragment } from '@/graphql/types'; @@ -589,11 +589,17 @@ function Flows() { <> {pageHeader}
- } - title="Loading flows..." - /> + + + + + + Loading flows... + + Please wait while we fetch your conversation flows + + +
); @@ -604,8 +610,15 @@ function Flows() { <> {pageHeader}
- + + + + + No flows found + Get started by creating your first conversation flow + + - } - description="Get started by creating your first conversation flow" - icon={} - title="No flows found" - /> + +
); diff --git a/frontend/src/pages/knowledges/knowledges.tsx b/frontend/src/pages/knowledges/knowledges.tsx index a56f332a..00703d1d 100644 --- a/frontend/src/pages/knowledges/knowledges.tsx +++ b/frontend/src/pages/knowledges/knowledges.tsx @@ -27,8 +27,8 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { InputSearch } from '@/components/ui/input-search'; -import { StatusCard } from '@/components/ui/status-card'; import { KnowledgeDocType } from '@/graphql/types'; import { useTableState } from '@/hooks/use-table-state'; import { routes } from '@/lib/routes'; @@ -404,11 +404,17 @@ function Knowledges() { <> {pageHeader}
- } - title="Loading knowledges..." - /> + + + + + + Loading knowledges... + + Please wait while we fetch your knowledge documents + + +
); @@ -419,8 +425,17 @@ function Knowledges() { <> {pageHeader}
- + + + + + No knowledge documents yet + + Create your first knowledge document to enrich the vector store + + + - } - description="Create your first knowledge document to enrich the vector store" - icon={} - title="No knowledge documents yet" - /> + +
); diff --git a/frontend/src/pages/settings/settings-account.tsx b/frontend/src/pages/settings/settings-account.tsx index 018719a3..cb153621 100644 --- a/frontend/src/pages/settings/settings-account.tsx +++ b/frontend/src/pages/settings/settings-account.tsx @@ -62,11 +62,11 @@ function SettingsAccount() {
{initial}
-
+
{displayName} - {memberSince && Member since {memberSince}} + {memberSince && Member since {memberSince}}
- {accountLabel} + {accountLabel} diff --git a/frontend/src/pages/settings/settings-api-tokens.tsx b/frontend/src/pages/settings/settings-api-tokens.tsx index b3e97242..af99afc2 100644 --- a/frontend/src/pages/settings/settings-api-tokens.tsx +++ b/frontend/src/pages/settings/settings-api-tokens.tsx @@ -24,7 +24,7 @@ import * as z from 'zod'; import type { ApiTokenFragmentFragment } from '@/graphql/types'; -import { AppHeader, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header'; +import { AppHeader, AppHeaderAction, AppHeaderActions, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header'; import ConfirmationDialog from '@/components/shared/confirmation-dialog'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; @@ -40,10 +40,10 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { Input } from '@/components/ui/input'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { StatusCard } from '@/components/ui/status-card'; import { ApiTokenCreatedDocument, ApiTokenDeletedDocument, @@ -144,45 +144,6 @@ const copyToClipboard = async (text: string): Promise => { } }; -function SettingsAPITokensHeader({ onCreateClick }: { onCreateClick: () => void }) { - return ( -
-
-

Manage API tokens for programmatic access

- -
- - -
- ); -} - const createNewTokenPlaceholder: APIToken = { createdAt: new Date().toISOString(), id: 'create-new', @@ -848,6 +809,51 @@ function SettingsAPITokens() { }>API Tokens + + } + label="Create Token" + onClick={handleCreateNew} + variant="secondary" + /> + + + + + + + + + GraphQL Playground + + + + + + Swagger UI + + + + + ); @@ -856,12 +862,15 @@ function SettingsAPITokens() { <> {pageHeader}
- - } - title="Loading tokens..." - /> + + + + + + Loading tokens... + Please wait while we fetch your API tokens + +
); @@ -872,7 +881,6 @@ function SettingsAPITokens() { <> {pageHeader}
- Error loading tokens @@ -890,9 +898,17 @@ function SettingsAPITokens() { <> {pageHeader}
- - + + + + + No API tokens configured + + Create your first API token to access PentAGI programmatically + + + - } - description="Create your first API token to access PentAGI programmatically" - icon={} - title="No API tokens configured" - /> + +
); @@ -914,8 +927,6 @@ function SettingsAPITokens() { <> {pageHeader}
- - {(createError || updateError || deleteError || deleteErrorMessage) && ( diff --git a/frontend/src/pages/settings/settings-prompt.tsx b/frontend/src/pages/settings/settings-prompt.tsx index 499d988a..43d682f4 100644 --- a/frontend/src/pages/settings/settings-prompt.tsx +++ b/frontend/src/pages/settings/settings-prompt.tsx @@ -68,9 +68,9 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { Form, FormControl, FormItem, FormMessage } from '@/components/ui/form'; import { FormSubmitButton } from '@/components/ui/form-submit-button'; -import { StatusCard } from '@/components/ui/status-card'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { CreatePromptDocument, @@ -733,11 +733,15 @@ function SettingsPrompt() { <> {pageHeader}
- } - title="Loading prompt data..." - /> + + + + + + Loading prompt data... + Please wait while we fetch prompt information + +
); @@ -748,11 +752,15 @@ function SettingsPrompt() { <> {pageHeader}
- } - title="Error loading prompt data" - /> + + + + + + Error loading prompt data + {error.message} + +
); @@ -763,11 +771,15 @@ function SettingsPrompt() { <> {pageHeader}
- } - title="Prompt not found" - /> + + + + + + Prompt not found + {`The prompt "${promptId}" could not be found or is not supported for editing.`} + +
); diff --git a/frontend/src/pages/settings/settings-prompts.tsx b/frontend/src/pages/settings/settings-prompts.tsx index 645b5377..32540de5 100644 --- a/frontend/src/pages/settings/settings-prompts.tsx +++ b/frontend/src/pages/settings/settings-prompts.tsx @@ -39,7 +39,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { StatusCard } from '@/components/ui/status-card'; +import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { DeletePromptDocument, SettingsPromptsDocument } from '@/graphql/types'; import { usePageStorageKeys } from '@/hooks/use-page-storage-keys'; import { routes } from '@/lib/routes'; @@ -323,8 +323,8 @@ function SettingsPrompts() { { accessorKey: 'displayName', cell: ({ row }) => ( -
- {row.original.displayName} +
+ {row.original.displayName}
), enableHiding: false, @@ -347,7 +347,6 @@ function SettingsPrompts() { ); }, meta: { columnMenuLabel: 'Agent Name', searchable: true }, - size: 200, }, { accessorKey: 'systemStatus', @@ -495,8 +494,8 @@ function SettingsPrompts() { { accessorKey: 'displayName', cell: ({ row }) => ( -
- {row.original.displayName} +
+ {row.original.displayName}
), enableHiding: false, @@ -519,7 +518,6 @@ function SettingsPrompts() { ); }, meta: { columnMenuLabel: 'Tool Name', searchable: true }, - size: 300, }, { accessorKey: 'status', @@ -793,11 +791,15 @@ function SettingsPrompts() { {pageHeader}
- } - title="Loading prompts..." - /> + + + + + + Loading prompts... + Please wait while we fetch your prompt templates + +
); @@ -828,11 +830,15 @@ function SettingsPrompts() { {pageHeader}
- } - title="No prompts available" - /> + + + + + + No prompts available + Prompt templates could not be loaded + +
); diff --git a/frontend/src/pages/settings/settings-provider.tsx b/frontend/src/pages/settings/settings-provider.tsx index e289754e..2ceb31e7 100644 --- a/frontend/src/pages/settings/settings-provider.tsx +++ b/frontend/src/pages/settings/settings-provider.tsx @@ -51,13 +51,13 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { FormSubmitButton } from '@/components/ui/form-submit-button'; import { Input } from '@/components/ui/input'; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from '@/components/ui/input-group'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import { StatusCard } from '@/components/ui/status-card'; import { AgentConfigType, CreateProviderDocument, @@ -1558,11 +1558,15 @@ function SettingsProvider() {
- } - title="Loading provider data..." - /> + + + + + + Loading provider data... + Please wait while we fetch provider configuration + +
); @@ -1579,11 +1583,15 @@ function SettingsProvider() {
- } - title="Error loading provider data" - /> + + + + + + Error loading provider data + {error.message} + +
); diff --git a/frontend/src/pages/settings/settings-providers.tsx b/frontend/src/pages/settings/settings-providers.tsx index 49fefbfd..5897940e 100644 --- a/frontend/src/pages/settings/settings-providers.tsx +++ b/frontend/src/pages/settings/settings-providers.tsx @@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom'; import type { ProviderConfigFragmentFragment } from '@/graphql/types'; import { providerIcons } from '@/components/icons/provider-icon'; -import { AppHeader, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header'; +import { AppHeader, AppHeaderActions, AppHeaderContent, AppHeaderTitle } from '@/components/layouts/app/app-header'; import ConfirmationDialog from '@/components/shared/confirmation-dialog'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; @@ -22,7 +22,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { StatusCard } from '@/components/ui/status-card'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { DeleteProviderDocument, ProviderType, SettingsProvidersDocument } from '@/graphql/types'; import { useTableState } from '@/hooks/use-table-state'; import { routes } from '@/lib/routes'; @@ -65,46 +65,39 @@ export function SettingsProvidersHeader() { }; return ( -
-

Manage language model providers

- - - - - - + + + + + {availableTypes.length === 0 ? ( + No available provider types + ) : ( + availableTypes.map(({ label, type }) => { + const Icon = providerIcons[type]?.icon; - return ( - handleProviderCreate(type)} - > - {Icon && } - {label} - - ); - }) - )} - - -
+ return ( + handleProviderCreate(type)} + > + {Icon && } + {label} + + ); + }) + )} + + ); } @@ -404,6 +397,9 @@ function SettingsProviders() { }>Providers + + + ); @@ -412,12 +408,17 @@ function SettingsProviders() { <> {pageHeader}
- - } - title="Loading providers..." - /> + + + + + + Loading providers... + + Please wait while we fetch your provider configurations + + +
); @@ -428,7 +429,6 @@ function SettingsProviders() { <> {pageHeader}
- Error loading providers @@ -446,9 +446,17 @@ function SettingsProviders() { <> {pageHeader}
- - + + + + + No providers configured + + Get started by adding your first language model provider + + + - } - description="Get started by adding your first language model provider" - icon={} - title="No providers configured" - /> + +
); @@ -470,8 +475,6 @@ function SettingsProviders() { <> {pageHeader}
- - {(deleteError || deleteErrorMessage) && ( diff --git a/frontend/src/pages/templates/templates.tsx b/frontend/src/pages/templates/templates.tsx index 39982e1e..da9fe392 100644 --- a/frontend/src/pages/templates/templates.tsx +++ b/frontend/src/pages/templates/templates.tsx @@ -24,7 +24,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { StatusCard } from '@/components/ui/status-card'; +import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'; import { useTableState } from '@/hooks/use-table-state'; import { routes } from '@/lib/routes'; import { mergeHrefWithSearchParams } from '@/lib/url-params'; @@ -268,8 +268,15 @@ function Templates() { <> {pageHeader}
- + + + + + No templates yet + Create your first template to get started + + - } - description="Create your first template to get started" - icon={} - title="No templates yet" - /> + +
);