diff --git a/frontend/src/app/agents/page.tsx b/frontend/src/app/agents/page.tsx index c0889076..c8c31952 100644 --- a/frontend/src/app/agents/page.tsx +++ b/frontend/src/app/agents/page.tsx @@ -28,6 +28,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state"; import { ApiError } from "@/api/mutator"; import { @@ -291,11 +292,7 @@ export default function AgentsPage() { {agentsQuery.isLoading ? ( - - - Loading… - - + ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( @@ -310,44 +307,29 @@ export default function AgentsPage() { )) ) : ( - - -
-
- - - - - - -
-

- No agents yet -

-

- Create your first agent to start executing tasks on - this board. -

- - Create your first agent - -
- - + + + + + + + } + title="No agents yet" + description="Create your first agent to start executing tasks on this board." + actionHref="/agents/new" + actionLabel="Create your first agent" + /> )} diff --git a/frontend/src/app/board-groups/page.tsx b/frontend/src/app/board-groups/page.tsx index 36525a18..80e7135c 100644 --- a/frontend/src/app/board-groups/page.tsx +++ b/frontend/src/app/board-groups/page.tsx @@ -35,6 +35,7 @@ import { } from "@/components/ui/dialog"; import { SignedOutPanel } from "@/components/auth/SignedOutPanel"; import { formatTimestamp } from "@/lib/formatters"; +import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state"; export default function BoardGroupsPage() { const { isSignedIn } = useAuth(); @@ -235,13 +236,7 @@ export default function BoardGroupsPage() { {groupsQuery.isLoading ? ( - - - - Loading… - - - + ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( )) ) : ( - - -
-
- - - - - - - -
-

- No groups yet -

-

- Create a board group to increase cross-board - visibility for agents. -

- - Create your first group - -
- - + + + + + + + + } + title="No groups yet" + description="Create a board group to increase cross-board visibility for agents." + actionHref="/board-groups/new" + actionLabel="Create your first group" + /> )} diff --git a/frontend/src/app/boards/page.tsx b/frontend/src/app/boards/page.tsx index 6c984f00..b6e67721 100644 --- a/frontend/src/app/boards/page.tsx +++ b/frontend/src/app/boards/page.tsx @@ -40,6 +40,7 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { SignedOutPanel } from "@/components/auth/SignedOutPanel"; +import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state"; const compactId = (value: string) => value.length > 8 ? `${value.slice(0, 8)}…` : value; @@ -290,13 +291,7 @@ export default function BoardsPage() { {boardsQuery.isLoading ? ( - - - - Loading… - - - + ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( )) ) : ( - - -
-
- - - - - - -
-

- No boards yet -

-

- Create your first board to start routing tasks and - monitoring work across agents. -

- - Create your first board - -
- - + + + + + + + } + title="No boards yet" + description="Create your first board to start routing tasks and monitoring work across agents." + actionHref="/boards/new" + actionLabel="Create your first board" + /> )} diff --git a/frontend/src/app/gateways/page.tsx b/frontend/src/app/gateways/page.tsx index b652e50a..ba1685db 100644 --- a/frontend/src/app/gateways/page.tsx +++ b/frontend/src/app/gateways/page.tsx @@ -26,6 +26,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state"; import { ApiError } from "@/api/mutator"; import { @@ -246,11 +247,7 @@ export default function GatewaysPage() { {gatewaysQuery.isLoading ? ( - - - Loading… - - + ) : table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( @@ -265,49 +262,27 @@ export default function GatewaysPage() { )) ) : ( - - -
-
- - - - -
-

- No gateways yet -

-

- Create your first gateway to connect boards and start - managing your OpenClaw connections. -

- - Create your first gateway - -
- - + + + + + } + title="No gateways yet" + description="Create your first gateway to connect boards and start managing your OpenClaw connections." + actionHref="/gateways/new" + actionLabel="Create your first gateway" + /> )} diff --git a/frontend/src/components/ui/table-state.tsx b/frontend/src/components/ui/table-state.tsx new file mode 100644 index 00000000..eb762fab --- /dev/null +++ b/frontend/src/components/ui/table-state.tsx @@ -0,0 +1,60 @@ +import type { ReactNode } from "react"; +import Link from "next/link"; + +import { buttonVariants } from "@/components/ui/button"; + +type TableLoadingRowProps = { + colSpan: number; + label?: string; +}; + +export function TableLoadingRow({ + colSpan, + label = "Loading…", +}: TableLoadingRowProps) { + return ( + + + {label} + + + ); +} + +type TableEmptyStateRowProps = { + colSpan: number; + icon: ReactNode; + title: string; + description: string; + actionHref?: string; + actionLabel?: string; +}; + +export function TableEmptyStateRow({ + colSpan, + icon, + title, + description, + actionHref, + actionLabel, +}: TableEmptyStateRowProps) { + return ( + + +
+
{icon}
+

{title}

+

{description}

+ {actionHref && actionLabel ? ( + + {actionLabel} + + ) : null} +
+ + + ); +}