33import { useMemo , useState , useEffect , useRef , useCallback } from "react" ;
44import ProposalCard from "@/components/proposals/ProposalCard" ;
55import type { Proposal } from "@/types" ;
6- import { FileText , Loader2 } from "lucide-react" ;
6+ import { FileText , Loader2 , Search , X } from "lucide-react" ;
77import { DAOTabLayout } from "@/components/aidaos/DAOTabLayout" ;
88import { enableSingleDaoMode , singleDaoName } from "@/config/features" ;
99import { useBatchProposalVotes } from "@/hooks/useBatchProposalVotes" ;
1010import { useQuery } from "@tanstack/react-query" ;
1111import { fetchLatestChainState } from "@/services/chain-state.service" ;
12+ import { Input } from "@/components/ui/input" ;
13+ import { Button } from "@/components/ui/button" ;
1214
1315const ITEMS_PER_BATCH = 5 ;
1416
@@ -25,9 +27,10 @@ const DAOProposals = ({
2527} : DAOProposalsProps ) => {
2628 const [ visibleCount , setVisibleCount ] = useState ( ITEMS_PER_BATCH ) ;
2729 const [ isLoading , setIsLoading ] = useState ( false ) ;
30+ const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
2831 const loaderRef = useRef < HTMLDivElement > ( null ) ;
2932
30- // Filter deployed proposals
33+ // Filter deployed proposals with search and status filter
3134 const deployedProposals = useMemo ( ( ) => {
3235 let filtered = proposals . filter (
3336 ( proposal ) => proposal . status === "DEPLOYED"
@@ -38,8 +41,24 @@ const DAOProposals = ({
3841 ) {
3942 filtered = [ ] ;
4043 }
44+
45+ // Apply search filter (search by username in reference link)
46+ if ( searchQuery . trim ( ) ) {
47+ const query = searchQuery . toLowerCase ( ) ;
48+ filtered = filtered . filter ( ( proposal ) => {
49+ // Extract reference link from content
50+ const referenceMatch = proposal . content ?. match (
51+ / R e f e r e n c e : \s * ( h t t p s ? : \/ \/ \S + ) / i
52+ ) ;
53+ const referenceLink = referenceMatch ?. [ 1 ] || "" ;
54+
55+ // Search in the reference link for username
56+ return referenceLink . toLowerCase ( ) . includes ( query ) ;
57+ } ) ;
58+ }
59+
4160 return filtered ;
42- } , [ proposals , daoName ] ) ;
61+ } , [ proposals , daoName , searchQuery ] ) ;
4362
4463 // Fetch chain state once for all proposals
4564 const { data : chainState } = useQuery ( {
@@ -99,19 +118,72 @@ const DAOProposals = ({
99118 setVisibleCount ( ITEMS_PER_BATCH ) ;
100119 } , [ proposals ] ) ;
101120
121+ // Reset visible count when search changes
122+ useEffect ( ( ) => {
123+ setVisibleCount ( ITEMS_PER_BATCH ) ;
124+ } , [ searchQuery ] ) ;
125+
126+ const clearSearch = ( ) => {
127+ setSearchQuery ( "" ) ;
128+ } ;
129+
130+ const hasActiveSearch = searchQuery . trim ( ) !== "" ;
131+
102132 return (
103133 < DAOTabLayout
104134 title = "Submission History"
105135 description = "Explore all the work that has been submitted to AIBTC."
106- isEmpty = { deployedProposals . length === 0 }
136+ isEmpty = { deployedProposals . length === 0 && ! hasActiveSearch }
107137 emptyTitle = "No Contributions Found"
108138 emptyIcon = { FileText }
139+ toolbar = {
140+ < div className = "relative w-full" >
141+ < Search className = "absolute left-3 sm:left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
142+ < Input
143+ placeholder = "Search contributions by X username"
144+ value = { searchQuery }
145+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
146+ className = "pl-9 border-none py-7"
147+ />
148+ { hasActiveSearch && (
149+ < Button
150+ variant = "ghost"
151+ size = "icon"
152+ onClick = { clearSearch }
153+ className = "absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7"
154+ title = "Clear search"
155+ >
156+ < X className = "h-4 w-4" />
157+ </ Button >
158+ ) }
159+ </ div >
160+ }
109161 >
110162 < div className = "space-y-4" >
163+ { /* Results count */ }
164+ { hasActiveSearch && (
165+ < div className = "text-sm text-muted-foreground px-4" >
166+ Found { deployedProposals . length } { " " }
167+ { deployedProposals . length === 1 ? "contribution" : "contributions" }
168+ </ div >
169+ ) }
111170 { isLoadingData && visibleProposals . length === 0 ? (
112171 < div className = "flex justify-center py-12" >
113172 < Loader2 className = "h-6 w-6 animate-spin text-muted-foreground" />
114173 </ div >
174+ ) : deployedProposals . length === 0 && hasActiveSearch ? (
175+ < div className = "flex flex-col items-center justify-center py-12 text-center" >
176+ < FileText className = "h-12 w-12 text-muted-foreground mb-4" />
177+ < h3 className = "text-lg font-semibold mb-2" >
178+ No contributions found
179+ </ h3 >
180+ < p className = "text-sm text-muted-foreground mb-4" >
181+ Try adjusting your search
182+ </ p >
183+ < Button variant = "outline" onClick = { clearSearch } >
184+ Clear search
185+ </ Button >
186+ </ div >
115187 ) : (
116188 < div className = "divide-y" >
117189 { visibleProposals . map ( ( proposal ) => {
0 commit comments