feat(frontend): update data fetching strategy and restructure dashboard page (#10265)

This plugin helps users organise library pages and use React Query for
data fetching on these pages.

### Changes

- Restructure the component position.
- Divide the component into two parts: one for rendering and the other
for hooks.
- Change data fetching from the normal fetch to an autogenerated React
query.
- Everything is shifted to the client side.

### Important Notes

- I haven’t changed any UI in this. I’ve divided it into sub-parts
because my main focus is on data fetching.
- Edit is not working, so I need to fix it in the follow-up PR. I
haven’t broken it; it broke already.
- I need to fix prop drilling in further PRs.
- I need to fix loading states.

> I haven’t changed the credit page or integration because I’m getting
errors while setting up Stripe for testing. My card is constantly
declined, and the integration page is attached to the builder page. I’ll
add changes to it when I’m working with the builder.

### Checklist 📋

- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
   - [x] Tested manually and everything is working perfectly
   - [x] Verified agent listing loads correctly
   - [x] Confirmed delete functionality works
This commit is contained in:
Abhimanyu Yadav
2025-07-08 18:51:22 +05:30
committed by GitHub
parent 5ff6d2ca56
commit 2183c94c58
10 changed files with 282 additions and 239 deletions

View File

@@ -1,9 +1,13 @@
"use client";
import * as React from "react";
import { AgentTableRow, AgentTableRowProps } from "./AgentTableRow";
import { AgentTableCard } from "./AgentTableCard";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
import { AgentTableCard } from "../AgentTableCard/AgentTableCard";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { useAgentTable } from "./useAgentTable";
import {
AgentTableRow,
AgentTableRowProps,
} from "../AgentTableRow/AgentTableRow";
export interface AgentTableProps {
agents: Omit<
@@ -22,22 +26,9 @@ export const AgentTable: React.FC<AgentTableProps> = ({
onEditSubmission,
onDeleteSubmission,
}) => {
// Use state to track selected agents
const [selectedAgents, setSelectedAgents] = React.useState<Set<string>>(
new Set(),
);
// Handle select all checkbox
const handleSelectAll = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedAgents(new Set(agents.map((agent) => agent.agent_id)));
} else {
setSelectedAgents(new Set());
}
},
[agents],
);
const { selectedAgents, handleSelectAll, setSelectedAgents } = useAgentTable({
agents,
});
return (
<div className="w-full">

View File

@@ -0,0 +1,25 @@
import { useState } from "react";
import { AgentTableRowProps } from "../AgentTableRow/AgentTableRow";
interface useAgentTableProps {
agents: Omit<
AgentTableRowProps,
| "setSelectedAgents"
| "selectedAgents"
| "onEditSubmission"
| "onDeleteSubmission"
>[];
}
export const useAgentTable = ({ agents }: useAgentTableProps) => {
const [selectedAgents, setSelectedAgents] = useState<Set<string>>(new Set());
const handleSelectAll = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedAgents(new Set(agents.map((agent) => agent.agent_id)));
} else {
setSelectedAgents(new Set());
}
};
return { selectedAgents, handleSelectAll, setSelectedAgents };
};

View File

@@ -1,10 +1,9 @@
"use client";
import * as React from "react";
import Image from "next/image";
import { IconStarFilled, IconMore } from "@/components/ui/icons";
import { Status, StatusType } from "./Status";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { Status, StatusType } from "@/components/agptui/Status";
export interface AgentTableCardProps {
agent_id: string;
@@ -21,7 +20,7 @@ export interface AgentTableCardProps {
onEditSubmission: (submission: StoreSubmissionRequest) => void;
}
export const AgentTableCard: React.FC<AgentTableCardProps> = ({
export const AgentTableCard = ({
agent_id,
agent_version,
agentName,
@@ -33,9 +32,8 @@ export const AgentTableCard: React.FC<AgentTableCardProps> = ({
runs,
rating,
onEditSubmission,
}) => {
}: AgentTableCardProps) => {
const onEdit = () => {
console.log("Edit agent", agentName);
onEditSubmission({
agent_id,
agent_version,

View File

@@ -1,12 +1,12 @@
"use client";
import * as React from "react";
import Image from "next/image";
import { IconStarFilled, IconMore, IconEdit } from "@/components/ui/icons";
import { Status, StatusType } from "./Status";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { TrashIcon } from "@radix-ui/react-icons";
import { StoreSubmissionRequest } from "@/lib/autogpt-server-api/types";
import { Status, StatusType } from "@/components/agptui/Status";
import { useAgentTableRow } from "./useAgentTableRow";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
export interface AgentTableRowProps {
agent_id: string;
@@ -27,7 +27,7 @@ export interface AgentTableRowProps {
onDeleteSubmission: (submission_id: string) => void;
}
export const AgentTableRow: React.FC<AgentTableRowProps> = ({
export const AgentTableRow = ({
agent_id,
agent_version,
agentName,
@@ -43,43 +43,21 @@ export const AgentTableRow: React.FC<AgentTableRowProps> = ({
setSelectedAgents,
onEditSubmission,
onDeleteSubmission,
}) => {
// Create a unique ID for the checkbox
const checkboxId = `agent-${id}-checkbox`;
const handleEdit = React.useCallback(() => {
onEditSubmission({
}: AgentTableRowProps) => {
const { checkboxId, handleEdit, handleDelete, handleCheckboxChange } =
useAgentTableRow({
id,
onEditSubmission,
onDeleteSubmission,
agent_id,
agent_version,
slug: "",
name: agentName,
agentName,
sub_heading,
description,
image_urls: imageSrc,
categories: [],
} satisfies StoreSubmissionRequest);
}, [
agent_id,
agent_version,
agentName,
sub_heading,
description,
imageSrc,
onEditSubmission,
]);
const handleDelete = React.useCallback(() => {
onDeleteSubmission(agent_id);
}, [agent_id, onDeleteSubmission]);
const handleCheckboxChange = React.useCallback(() => {
if (selectedAgents.has(agent_id)) {
selectedAgents.delete(agent_id);
} else {
selectedAgents.add(agent_id);
}
setSelectedAgents(new Set(selectedAgents));
}, [agent_id, selectedAgents, setSelectedAgents]);
imageSrc,
selectedAgents,
setSelectedAgents,
});
return (
<div className="hidden items-center border-b border-neutral-300 px-4 py-4 hover:bg-neutral-50 dark:border-neutral-700 dark:hover:bg-neutral-800 md:flex">

View File

@@ -0,0 +1,59 @@
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
interface useAgentTableRowProps {
id: number;
onEditSubmission: (submission: StoreSubmissionRequest) => void;
onDeleteSubmission: (submission_id: string) => void;
agent_id: string;
agent_version: number;
agentName: string;
sub_heading: string;
description: string;
imageSrc: string[];
selectedAgents: Set<string>;
setSelectedAgents: React.Dispatch<React.SetStateAction<Set<string>>>;
}
export const useAgentTableRow = ({
id,
onEditSubmission,
onDeleteSubmission,
agent_id,
agent_version,
agentName,
sub_heading,
description,
imageSrc,
selectedAgents,
setSelectedAgents,
}: useAgentTableRowProps) => {
const checkboxId = `agent-${id}-checkbox`;
const handleEdit = () => {
onEditSubmission({
agent_id,
agent_version,
slug: "",
name: agentName,
sub_heading,
description,
image_urls: imageSrc,
categories: [],
} satisfies StoreSubmissionRequest);
};
const handleDelete = () => {
onDeleteSubmission(agent_id);
};
const handleCheckboxChange = () => {
if (selectedAgents.has(agent_id)) {
selectedAgents.delete(agent_id);
} else {
selectedAgents.add(agent_id);
}
setSelectedAgents(new Set(selectedAgents));
};
return { checkboxId, handleEdit, handleDelete, handleCheckboxChange };
};

View File

@@ -0,0 +1,91 @@
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
import { Button } from "@/components/ui/button";
import { useMainDashboardPage } from "./useMainDashboardPage";
import { Separator } from "@/components/ui/separator";
import { AgentTable } from "../AgentTable/AgentTable";
import { StatusType } from "@/components/agptui/Status";
export const MainDashboardPage = () => {
const {
onOpenPopout,
onDeleteSubmission,
onEditSubmission,
submissions,
isLoading,
openPopout,
submissionData,
popoutStep,
} = useMainDashboardPage();
if (isLoading) {
return "Loading....";
}
return (
<main className="flex-1 py-8">
{/* Header Section */}
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-6">
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
Agent dashboard
</h1>
<div className="space-y-2">
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
Submit a New Agent
</h2>
<p className="text-sm text-[#707070] dark:text-neutral-400">
Select from the list of agents you currently have, or upload from
your local machine.
</p>
</div>
</div>
<PublishAgentPopout
trigger={
<Button
onClick={onOpenPopout}
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
>
Submit agent
</Button>
}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
</div>
<Separator className="mb-8" />
{/* Agents Section */}
<div>
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
/>
)}
</div>
</main>
);
};

View File

@@ -0,0 +1,72 @@
import {
getGetV2ListMySubmissionsQueryKey,
useDeleteV2DeleteStoreSubmission,
useGetV2ListMySubmissions,
} from "@/app/api/__generated__/endpoints/store/store";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
import { StoreSubmissionsResponse } from "@/app/api/__generated__/models/storeSubmissionsResponse";
import { getQueryClient } from "@/lib/react-query/queryClient";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useState } from "react";
export const useMainDashboardPage = () => {
const queryClient = getQueryClient();
const { user } = useSupabase();
const [openPopout, setOpenPopout] = useState<boolean>(false);
const [submissionData, setSubmissionData] =
useState<StoreSubmissionRequest>();
const [popoutStep, setPopoutStep] = useState<"select" | "info" | "review">(
"info",
);
const { mutateAsync: deleteSubmission } = useDeleteV2DeleteStoreSubmission({
mutation: {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: getGetV2ListMySubmissionsQueryKey(),
});
},
},
});
const { data: submissions, isLoading } = useGetV2ListMySubmissions(
undefined,
{
query: {
select: (x) => {
return x.data as StoreSubmissionsResponse;
},
enabled: !!user,
},
},
);
const onEditSubmission = (submission: StoreSubmissionRequest) => {
setSubmissionData(submission);
setPopoutStep("review");
setOpenPopout(true);
};
const onDeleteSubmission = async (submission_id: string) => {
await deleteSubmission({
submissionId: submission_id,
});
};
const onOpenPopout = () => {
setPopoutStep("select");
setOpenPopout(true);
};
return {
onOpenPopout,
onDeleteSubmission,
onEditSubmission,
submissions,
isLoading,
openPopout,
submissionData,
popoutStep,
};
};

View File

@@ -1,133 +1,7 @@
"use client";
import * as React from "react";
import { AgentTable } from "@/components/agptui/AgentTable";
import { Button } from "@/components/agptui/Button";
import { Separator } from "@/components/ui/separator";
import { StatusType } from "@/components/agptui/Status";
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
import { useCallback, useEffect, useState } from "react";
import {
StoreSubmissionsResponse,
StoreSubmissionRequest,
} from "@/lib/autogpt-server-api/types";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { MainDashboardPage } from "./components/MainDashboardPage/MainDashboardPage";
export default function Page() {
const { supabase } = useSupabase();
const api = useBackendAPI();
const [submissions, setSubmissions] = useState<StoreSubmissionsResponse>();
const [openPopout, setOpenPopout] = useState<boolean>(false);
const [submissionData, setSubmissionData] =
useState<StoreSubmissionRequest>();
const [popoutStep, setPopoutStep] = useState<"select" | "info" | "review">(
"info",
);
const fetchData = useCallback(async () => {
try {
const submissions = await api.getStoreSubmissions();
setSubmissions(submissions);
} catch (error) {
console.error("Error fetching submissions:", error);
}
}, [api]);
useEffect(() => {
if (!supabase) {
return;
}
fetchData();
}, [supabase, fetchData]);
const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => {
setSubmissionData(submission);
setPopoutStep("review");
setOpenPopout(true);
}, []);
const onDeleteSubmission = useCallback(
(submission_id: string) => {
if (!supabase) {
return;
}
api.deleteStoreSubmission(submission_id);
fetchData();
},
[api, supabase, fetchData],
);
const onOpenPopout = useCallback(() => {
setPopoutStep("select");
setOpenPopout(true);
}, []);
return (
<main className="flex-1 py-8">
{/* Header Section */}
<div className="mb-8 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-6">
<h1 className="text-4xl font-medium text-neutral-900 dark:text-neutral-100">
Agent dashboard
</h1>
<div className="space-y-2">
<h2 className="text-xl font-medium text-neutral-900 dark:text-neutral-100">
Submit a New Agent
</h2>
<p className="text-sm text-[#707070] dark:text-neutral-400">
Select from the list of agents you currently have, or upload from
your local machine.
</p>
</div>
</div>
<PublishAgentPopout
trigger={
<Button
onClick={onOpenPopout}
className="h-9 rounded-full bg-black px-4 text-sm font-medium text-white hover:bg-neutral-700 dark:hover:bg-neutral-600"
>
Submit agent
</Button>
}
openPopout={openPopout}
inputStep={popoutStep}
submissionData={submissionData}
/>
</div>
<Separator className="mb-8" />
{/* Agents Section */}
<div>
<h2 className="mb-4 text-xl font-bold text-neutral-900 dark:text-neutral-100">
Your uploaded agents
</h2>
{submissions && (
<AgentTable
agents={
submissions?.submissions.map((submission, index) => ({
id: index,
agent_id: submission.agent_id,
agent_version: submission.agent_version,
sub_heading: submission.sub_heading,
date_submitted: submission.date_submitted,
agentName: submission.name,
description: submission.description,
imageSrc: submission.image_urls || [""],
dateSubmitted: new Date(
submission.date_submitted,
).toLocaleDateString(),
status: submission.status.toLowerCase() as StatusType,
runs: submission.runs,
rating: submission.rating,
})) || []
}
onEditSubmission={onEditSubmission}
onDeleteSubmission={onDeleteSubmission}
/>
)}
</div>
</main>
);
return <MainDashboardPage />;
}

View File

@@ -3237,48 +3237,6 @@
}
}
},
"/api/library/agents/by-graph/{graph_id}": {
"get": {
"tags": ["v2", "library", "private"],
"summary": "Get Library Agent By Graph Id",
"operationId": "getV2GetLibraryAgentByGraphId",
"parameters": [
{
"name": "graph_id",
"in": "path",
"required": true,
"schema": { "type": "string", "title": "Graph Id" }
},
{
"name": "version",
"in": "query",
"required": false,
"schema": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
"title": "Version"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/LibraryAgent" }
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
}
}
}
}
}
},
"/api/library/agents/marketplace/{store_listing_version_id}": {
"get": {
"tags": ["v2", "library", "private", "store, library"],
@@ -5184,7 +5142,6 @@
"AGENT_INPUT",
"CONGRATS",
"GET_RESULTS",
"RUN_AGENTS",
"MARKETPLACE_VISIT",
"MARKETPLACE_ADD_AGENT",
"MARKETPLACE_RUN_AGENT",

View File

@@ -14,13 +14,11 @@ import {
} from "../PublishAgentSelectInfo";
import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview";
import { Button } from "../Button";
import {
StoreSubmissionRequest,
MyAgentsResponse,
} from "@/lib/autogpt-server-api";
import { MyAgentsResponse } from "@/lib/autogpt-server-api";
import { useRouter } from "next/navigation";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import { useToast } from "@/components/ui/use-toast";
import { StoreSubmissionRequest } from "@/app/api/__generated__/models/storeSubmissionRequest";
interface PublishAgentPopoutProps {
trigger?: React.ReactNode;
openPopout?: boolean;
@@ -263,8 +261,8 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
<PublishAgentAwaitingReview
agentName={publishData.name}
subheader={publishData.sub_heading}
description={publishData.description}
thumbnailSrc={publishData.image_urls[0]}
description={publishData.description || ""}
thumbnailSrc={publishData.image_urls?.[0]}
onClose={handleClose}
onDone={handleClose}
onViewProgress={() => {