1- import React , { useState , useEffect } from 'react' ;
1+ import React , { useState , useEffect , useMemo } from 'react' ;
22import { useNavigate } from 'react-router-dom' ;
33import { motion } from 'framer-motion' ;
4- import { User , Building , BookOpen } from 'lucide-react' ;
4+ import { User , Building , BookOpen , Search } from 'lucide-react' ;
55
66import Header from '@/sections/Header' ;
77import Footer from '@/sections/Footer' ;
@@ -17,6 +17,16 @@ const AuthorsPage: React.FC = () => {
1717 const [ authors , setAuthors ] = useState < AuthorWithPostCount [ ] > ( [ ] ) ;
1818 const [ isLoading , setIsLoading ] = useState ( true ) ;
1919 const [ error , setError ] = useState < string | null > ( null ) ;
20+ const [ searchTerm , setSearchTerm ] = useState ( '' ) ;
21+ const [ debouncedSearch , setDebouncedSearch ] = useState ( '' ) ;
22+
23+ // Debounce search input
24+ useEffect ( ( ) => {
25+ const handler = setTimeout ( ( ) => {
26+ setDebouncedSearch ( searchTerm . trim ( ) . toLowerCase ( ) ) ;
27+ } , 300 ) ;
28+ return ( ) => clearTimeout ( handler ) ;
29+ } , [ searchTerm ] ) ;
2030
2131 useEffect ( ( ) => {
2232 const loadAllAuthors = async ( ) => {
@@ -61,6 +71,14 @@ const AuthorsPage: React.FC = () => {
6171 navigate ( `/authors/${ slug } ` ) ;
6272 } ;
6373
74+ // Filter authors based on search
75+ const filteredAuthors = useMemo ( ( ) => {
76+ if ( ! debouncedSearch ) return authors ;
77+ return authors . filter ( ( author ) =>
78+ author . name . toLowerCase ( ) . includes ( debouncedSearch ) ,
79+ ) ;
80+ } , [ authors , debouncedSearch ] ) ;
81+
6482 const renderContent = ( ) => {
6583 if ( isLoading ) {
6684 return (
@@ -95,88 +113,113 @@ const AuthorsPage: React.FC = () => {
95113 ) ;
96114 }
97115
116+ // Search bar
98117 return (
99- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" >
100- { authors . map ( ( author , index ) => (
101- < motion . div
102- key = { author . slug }
103- onClick = { ( ) => handleAuthorClick ( author . slug ) }
104- className = "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/20
105- transition-shadow duration-300 group-hover:shadow-blue-200 dark:group-hover:shadow-blue-500/30
106- cursor-pointer group flex flex-col"
107- initial = { { opacity : 0 , y : 20 } }
108- animate = { { opacity : 1 , y : 0 } }
109- transition = { {
110- duration : 0.5 ,
111- delay : index * 0.1 ,
112- scale : {
113- type : 'spring' ,
114- stiffness : 300 ,
115- damping : 15 ,
116- } ,
117- } }
118- whileHover = { { scale : 1.05 } }
119- >
120- < div className = "flex flex-col items-center p-6 text-center flex-1" >
121- { /* Avatar */ }
122- < div className = "flex-shrink-0 mb-4" >
123- { author . avatar ? (
124- < img
125- src = { author . avatar }
126- alt = { author . name }
127- className = "w-24 h-24 rounded-full object-cover border-4 border-blue-100 dark:border-blue-900"
128- />
129- ) : (
130- < div className = "w-24 h-24 bg-blue-100 dark:bg-gray-700/50 rounded-full flex items-center justify-center" >
131- < User className = "w-12 h-12 text-blue-600 dark:text-blue-400" />
118+ < div >
119+ < div className = "flex justify-center mb-12" >
120+ < div className = "relative w-full max-w-md" >
121+ < Search className = "absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-500 dark:text-gray-400 w-5 h-5" />
122+ < input
123+ type = "text"
124+ placeholder = "Search authors by name..."
125+ className = "w-full pl-12 pr-4 py-3 rounded-full border border-gray-300 dark:border-gray-700
126+ bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200
127+ focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
128+ value = { searchTerm }
129+ onChange = { ( e ) => setSearchTerm ( e . target . value ) }
130+ />
131+ </ div >
132+ </ div >
133+
134+ { filteredAuthors . length === 0 ? (
135+ < div className = "text-center py-16 text-gray-600 dark:text-gray-300" >
136+ No authors found matching "
137+ < span className = "font-semibold text-blue-600 dark:text-blue-400" >
138+ { searchTerm }
139+ </ span >
140+ "
141+ </ div >
142+ ) : (
143+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" >
144+ { filteredAuthors . map ( ( author , index ) => (
145+ < motion . div
146+ key = { author . slug }
147+ onClick = { ( ) => handleAuthorClick ( author . slug ) }
148+ className = "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/20
149+ transition-shadow duration-300 group-hover:shadow-blue-200 dark:group-hover:shadow-blue-500/30
150+ cursor-pointer group flex flex-col"
151+ initial = { { opacity : 0 , y : 20 } }
152+ animate = { { opacity : 1 , y : 0 } }
153+ transition = { {
154+ duration : 0.5 ,
155+ delay : index * 0.1 ,
156+ } }
157+ whileHover = { { scale : 1.05 } }
158+ >
159+ < div className = "flex flex-col items-center p-6 text-center flex-1" >
160+ { /* Avatar */ }
161+ < div className = "flex-shrink-0 mb-4" >
162+ { author . avatar ? (
163+ < img
164+ src = { author . avatar }
165+ alt = { author . name }
166+ className = "w-24 h-24 rounded-full object-cover border-4 border-blue-100 dark:border-blue-900"
167+ />
168+ ) : (
169+ < div className = "w-24 h-24 bg-blue-100 dark:bg-gray-700/50 rounded-full flex items-center justify-center" >
170+ < User className = "w-12 h-12 text-blue-600 dark:text-blue-400" />
171+ </ div >
172+ ) }
132173 </ div >
133- ) }
134- </ div >
135-
136- { /* Author Info*/ }
137- < div className = "flex flex-col flex-1" >
138- < h1 className = "text-xl font-bold text-gray-900 dark:text-white mb-1 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors" >
139- { author . name }
140- </ h1 >
141-
142- { /* Title and Org */ }
143- < div className = "flex flex-col items-center gap-1 mb-3" >
144- < span className = "text-md text-blue-600 dark:text-blue-400 font-medium" >
145- { author . title }
146- </ span >
147- { author . organization && (
148- < div className = "flex items-center justify-center gap-1 text-sm text-gray-700 dark:text-gray-300" >
149- < Building className = "w-4 h-4" />
150- < span className = "font-medium" > { author . organization } </ span >
174+
175+ { /* Author Info*/ }
176+ < div className = "flex flex-col flex-1" >
177+ < h1 className = "text-xl font-bold text-gray-900 dark:text-white mb-1 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors" >
178+ { author . name }
179+ </ h1 >
180+
181+ { /* Title and Org */ }
182+ < div className = "flex flex-col items-center gap-1 mb-3" >
183+ < span className = "text-md text-blue-600 dark:text-blue-400 font-medium" >
184+ { author . title }
185+ </ span >
186+ { author . organization && (
187+ < div className = "flex items-center justify-center gap-1 text-sm text-gray-700 dark:text-gray-300" >
188+ < Building className = "w-4 h-4" />
189+ < span className = "font-medium" >
190+ { author . organization }
191+ </ span >
192+ </ div >
193+ ) }
151194 </ div >
152- ) }
153- </ div >
154195
155- { /* Description */ }
156- < p className = "text-gray-600 dark:text-gray-300 text-sm mb-4 max-w-xl line-clamp-3" >
157- { author . description }
158- </ p >
159-
160- { /* Quick Stats */ }
161- < div className = "flex flex-wrap justify-center gap-2 text-xs text-gray-600 dark:text-gray-300 mt-auto" >
162- < div className = "flex items-center gap-1 bg-blue-50 dark:bg-blue-900/30 px-3 py-1 rounded-full" >
163- < BookOpen className = "w-4 h-4" />
164- < span >
165- { author . postCount } { ' ' }
166- { author . postCount === 1 ? 'Article' : 'Articles' }
167- </ span >
168- </ div >
169- { author . organization && (
170- < div className = "flex items-center gap-1 bg-gray-50 dark:bg-gray-700/50 px-3 py-1 rounded-full" >
171- < Building className = "w-4 h-4" />
172- < span > { author . organization } </ span >
196+ { /* Description */ }
197+ < p className = "text-gray-600 dark:text-gray-300 text-sm mb-4 max-w-xl line-clamp-3" >
198+ { author . description }
199+ </ p >
200+
201+ { /* Quick Stats */ }
202+ < div className = "flex flex-wrap justify-center gap-2 text-xs text-gray-600 dark:text-gray-300 mt-auto" >
203+ < div className = "flex items-center gap-1 bg-blue-50 dark:bg-blue-900/30 px-3 py-1 rounded-full" >
204+ < BookOpen className = "w-4 h-4" />
205+ < span >
206+ { author . postCount } { ' ' }
207+ { author . postCount === 1 ? 'Article' : 'Articles' }
208+ </ span >
209+ </ div >
210+ { author . organization && (
211+ < div className = "flex items-center gap-1 bg-gray-50 dark:bg-gray-700/50 px-3 py-1 rounded-full" >
212+ < Building className = "w-4 h-4" />
213+ < span > { author . organization } </ span >
214+ </ div >
215+ ) }
173216 </ div >
174- ) }
217+ </ div >
175218 </ div >
176- </ div >
177- </ div >
178- </ motion . div >
179- ) ) }
219+ </ motion . div >
220+ ) ) }
221+ </ div >
222+ ) }
180223 </ div >
181224 ) ;
182225 } ;
0 commit comments