Skip to content

Commit a730697

Browse files
authored
refactor(frontend): simplify marketplace search page and update data fetching (#11061)
This PR refactors the marketplace search page to improve code maintainability, readability, and follows modern React patterns by extracting complex logic into a custom hook and creating dedicated components. ### 🔄 Changes #### **Architecture Improvements** - **Component Extraction**: Replaced the monolithic `SearchResults` component with a cleaner `MainSearchResultPage` component that focuses solely on presentation - **Custom Hook Pattern**: Extracted all business logic and state management into `useMainSearchResultPage` hook for better separation of concerns - **Loading State Component**: Added dedicated `MainSearchResultPageLoading` component for consistent loading UI #### **Code Simplification** - **Reduced search page to 19 lines** (from 175 lines) by removing inline logic and state management - **Centralized data fetching** using auto-generated API endpoints (`useGetV2ListStoreAgents`, `useGetV2ListStoreCreators`) - **Improved error handling** with dedicated error states and loading states #### **Feature Updates** - **Sort Options**: Commented out "Most Recent" and "Highest Rated" sort options due to backend limitations (no date/rating data available) - **Client-side Sorting**: Implemented client-side sorting for "runs" and "rating" as a temporary solution - **Search Filters**: Maintained filter functionality for agents/creators with improved state management ### 📊 Impact - **Better Developer Experience**: Code is now more modular and easier to understand - **Improved Maintainability**: Business logic separated from presentation layer - **Future-Ready**: Structure prepared for backend improvements when date/rating data becomes available - **Type Safety**: Leveraging TypeScript with auto-generated API types ### 🧪 Testing Checklist - [x] Search functionality works correctly with various search terms - [x] Filter chips correctly toggle between "All", "Agents", and "Creators" - [x] Sort dropdown displays only "Most Runs" option - [x] Client-side sorting correctly sorts agents and creators by runs - [x] Loading state displays while fetching data - [x] Error state displays when API calls fail - [x] "No results found" message appears for empty searches - [x] Search bar in results page is functional - [x] Results display correctly with proper layout and styling
1 parent c42f94c commit a730697

File tree

6 files changed

+269
-175
lines changed

6 files changed

+269
-175
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { SearchBar } from "@/components/__legacy__/SearchBar";
2+
import { useMainSearchResultPage } from "./useMainSearchResultPage";
3+
import { SearchFilterChips } from "@/components/__legacy__/SearchFilterChips";
4+
import { SortDropdown } from "@/components/__legacy__/SortDropdown";
5+
import { AgentsSection } from "../AgentsSection/AgentsSection";
6+
import { Separator } from "@/components/__legacy__/ui/separator";
7+
import { FeaturedCreators } from "../FeaturedCreators/FeaturedCreators";
8+
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
9+
import { MainMarketplacePageLoading } from "../MainMarketplacePageLoading";
10+
11+
export const MainSearchResultPage = ({
12+
searchTerm,
13+
sort,
14+
}: {
15+
searchTerm: string;
16+
sort: string;
17+
}) => {
18+
const {
19+
agents,
20+
creators,
21+
totalCount,
22+
agentsCount,
23+
creatorsCount,
24+
handleFilterChange,
25+
handleSortChange,
26+
showAgents,
27+
showCreators,
28+
isAgentsLoading,
29+
isCreatorsLoading,
30+
isAgentsError,
31+
isCreatorsError,
32+
} = useMainSearchResultPage({ searchTerm, sort });
33+
34+
const isLoading = isAgentsLoading || isCreatorsLoading;
35+
const hasError = isAgentsError || isCreatorsError;
36+
37+
if (isLoading) {
38+
return <MainMarketplacePageLoading />;
39+
}
40+
41+
if (hasError) {
42+
return (
43+
<div className="flex min-h-[500px] items-center justify-center">
44+
<ErrorCard
45+
isSuccess={false}
46+
responseError={{ message: "Failed to load marketplace data" }}
47+
context="marketplace page"
48+
onRetry={() => window.location.reload()}
49+
/>
50+
</div>
51+
);
52+
}
53+
return (
54+
<div className="w-full">
55+
<div className="mx-auto min-h-screen max-w-[1440px] px-10 lg:min-w-[1440px]">
56+
<div className="mt-8 flex items-center">
57+
<div className="flex-1">
58+
<h2 className="text-base font-medium leading-normal text-neutral-800 dark:text-neutral-200">
59+
Results for:
60+
</h2>
61+
<h1 className="font-poppins text-2xl font-semibold leading-[32px] text-neutral-800 dark:text-neutral-100">
62+
{searchTerm}
63+
</h1>
64+
</div>
65+
<div className="flex-none">
66+
<SearchBar width="w-[439px]" height="h-[60px]" />
67+
</div>
68+
</div>
69+
70+
{totalCount > 0 ? (
71+
<>
72+
<div className="mt-[36px] flex items-center justify-between">
73+
<SearchFilterChips
74+
totalCount={totalCount}
75+
agentsCount={agentsCount}
76+
creatorsCount={creatorsCount}
77+
onFilterChange={handleFilterChange}
78+
/>
79+
<SortDropdown onSort={handleSortChange} />
80+
</div>
81+
{/* Content section */}
82+
<div className="min-h-[500px] max-w-[1440px] space-y-8 py-8">
83+
{showAgents && agentsCount > 0 && agents && (
84+
<div className="mt-[36px]">
85+
<AgentsSection agents={agents} sectionTitle="Agents" />
86+
</div>
87+
)}
88+
89+
{showAgents && agentsCount > 0 && creatorsCount > 0 && (
90+
<Separator />
91+
)}
92+
{showCreators && creatorsCount > 0 && creators && (
93+
<FeaturedCreators
94+
featuredCreators={creators}
95+
title="Creators"
96+
/>
97+
)}
98+
</div>
99+
</>
100+
) : (
101+
<div className="mt-20 flex flex-col items-center justify-center">
102+
<h3 className="mb-2 text-xl font-medium text-neutral-600 dark:text-neutral-300">
103+
No results found
104+
</h3>
105+
<p className="text-neutral-500 dark:text-neutral-400">
106+
Try adjusting your search terms or filters
107+
</p>
108+
</div>
109+
)}
110+
</div>
111+
</div>
112+
);
113+
};
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import {
2+
useGetV2ListStoreAgents,
3+
useGetV2ListStoreCreators,
4+
} from "@/app/api/__generated__/endpoints/store/store";
5+
import { CreatorsResponse } from "@/app/api/__generated__/models/creatorsResponse";
6+
import { StoreAgentsResponse } from "@/app/api/__generated__/models/storeAgentsResponse";
7+
import { useState, useMemo } from "react";
8+
9+
interface useMainSearchResultPageType {
10+
searchTerm: string;
11+
sort: string;
12+
}
13+
14+
export const useMainSearchResultPage = ({
15+
searchTerm,
16+
sort,
17+
}: useMainSearchResultPageType) => {
18+
const [showAgents, setShowAgents] = useState(true);
19+
const [showCreators, setShowCreators] = useState(true);
20+
const [clientSortBy, setClientSortBy] = useState<string>(sort);
21+
22+
const {
23+
data: agentsData,
24+
isLoading: isAgentsLoading,
25+
isError: isAgentsError,
26+
} = useGetV2ListStoreAgents(
27+
{
28+
search_query: searchTerm,
29+
sorted_by: sort,
30+
},
31+
{
32+
query: {
33+
select: (x) => {
34+
return (x.data as StoreAgentsResponse).agents;
35+
},
36+
},
37+
},
38+
);
39+
40+
const {
41+
data: creatorsData,
42+
isLoading: isCreatorsLoading,
43+
isError: isCreatorsError,
44+
} = useGetV2ListStoreCreators(
45+
{ search_query: searchTerm, sorted_by: sort },
46+
{
47+
query: {
48+
select: (x) => {
49+
return (x.data as CreatorsResponse).creators;
50+
},
51+
},
52+
},
53+
);
54+
55+
// This is the strategy, we are using for sorting the agents and creators.
56+
// currently we are doing it client side but maybe we will shift it to the server side.
57+
// we will store the sortBy state in the url params, and then refetch the data with the new sortBy.
58+
59+
const agents = useMemo(() => {
60+
if (!agentsData) return [];
61+
62+
const sorted = [...agentsData];
63+
64+
if (clientSortBy === "runs") {
65+
return sorted.sort((a, b) => b.runs - a.runs);
66+
} else if (clientSortBy === "rating") {
67+
return sorted.sort((a, b) => b.rating - a.rating);
68+
} else {
69+
return sorted;
70+
}
71+
}, [agentsData, clientSortBy]);
72+
73+
const creators = useMemo(() => {
74+
if (!creatorsData) return [];
75+
76+
const sorted = [...creatorsData];
77+
78+
if (clientSortBy === "runs") {
79+
return sorted.sort((a, b) => b.agent_runs - a.agent_runs);
80+
} else if (clientSortBy === "rating") {
81+
return sorted.sort((a, b) => b.agent_rating - a.agent_rating);
82+
} else {
83+
return sorted.sort((a, b) => b.num_agents - a.num_agents);
84+
}
85+
}, [creatorsData, clientSortBy]);
86+
87+
const agentsCount = agents?.length ?? 0;
88+
const creatorsCount = creators?.length ?? 0;
89+
const totalCount = agentsCount + creatorsCount;
90+
91+
const handleFilterChange = (value: string) => {
92+
if (value === "agents") {
93+
setShowAgents(true);
94+
setShowCreators(false);
95+
} else if (value === "creators") {
96+
setShowAgents(false);
97+
setShowCreators(true);
98+
} else {
99+
setShowAgents(true);
100+
setShowCreators(true);
101+
}
102+
};
103+
104+
const handleSortChange = (sortValue: string) => {
105+
setClientSortBy(sortValue);
106+
};
107+
108+
return {
109+
agents,
110+
creators,
111+
handleFilterChange,
112+
handleSortChange,
113+
agentsCount,
114+
creatorsCount,
115+
totalCount,
116+
showAgents,
117+
showCreators,
118+
isAgentsLoading,
119+
isCreatorsLoading,
120+
isAgentsError,
121+
isCreatorsError,
122+
};
123+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Skeleton } from "@/components/__legacy__/ui/skeleton";
2+
3+
export const MainSearchResultPageLoading = () => {
4+
return (
5+
<div className="w-full">
6+
<div className="mx-auto min-h-screen max-w-[1440px] px-10 lg:min-w-[1440px]">
7+
<div className="mt-8 flex items-center">
8+
<div className="flex-1">
9+
<Skeleton className="mb-2 h-5 w-32 bg-neutral-200 dark:bg-neutral-700" />
10+
<Skeleton className="h-8 w-64 bg-neutral-200 dark:bg-neutral-700" />
11+
</div>
12+
<div className="flex-none">
13+
<Skeleton className="h-[60px] w-[439px] bg-neutral-200 dark:bg-neutral-700" />
14+
</div>
15+
</div>
16+
<div className="mt-[36px] flex items-center justify-between">
17+
<Skeleton className="h-8 w-48 bg-neutral-200 dark:bg-neutral-700" />
18+
<Skeleton className="h-8 w-32 bg-neutral-200 dark:bg-neutral-700" />
19+
</div>
20+
<div className="mt-20 flex flex-col items-center justify-center">
21+
<Skeleton className="mb-4 h-6 w-40 bg-neutral-200 dark:bg-neutral-700" />
22+
<Skeleton className="h-6 w-80 bg-neutral-200 dark:bg-neutral-700" />
23+
</div>
24+
</div>
25+
</div>
26+
);
27+
};

0 commit comments

Comments
 (0)