1+ import React , { useState , useEffect } from 'react' ;
2+ import { useNavigate } from 'react-router-dom' ;
3+ import { motion } from 'framer-motion' ;
4+ import { User , Building , BookOpen } from 'lucide-react' ;
5+
6+ import Header from '@/sections/Header' ;
7+ import Footer from '@/sections/Footer' ;
8+ import { fetchAllAuthors , Author } from '@/utils/author-utils' ;
9+ import { getPostsByAuthor } from '@/utils/posts-utils' ;
10+
11+ type AuthorWithPostCount = Author & {
12+ postCount : number ;
13+ } ;
14+
15+ const AuthorsPage : React . FC = ( ) => {
16+ const navigate = useNavigate ( ) ;
17+ const [ authors , setAuthors ] = useState < AuthorWithPostCount [ ] > ( [ ] ) ;
18+ const [ isLoading , setIsLoading ] = useState ( true ) ;
19+ const [ error , setError ] = useState < string | null > ( null ) ;
20+
21+ useEffect ( ( ) => {
22+ const loadAllAuthors = async ( ) => {
23+ setIsLoading ( true ) ;
24+ document . title = 'Our Authors - SugarLabs' ;
25+ try {
26+
27+ const allAuthors = await fetchAllAuthors ( ) ;
28+
29+ if ( ! allAuthors || allAuthors . length === 0 ) {
30+ setAuthors ( [ ] ) ;
31+ return ;
32+ }
33+ console . log ( allAuthors ) ;
34+ const authorsWithData = await Promise . all (
35+ allAuthors . map ( async ( author ) => {
36+ try {
37+ const posts = await getPostsByAuthor ( author . slug ) ;
38+ return { ...author , postCount : posts . length } ;
39+ } catch ( postError ) {
40+ console . error (
41+ `Failed to get post count for ${ author . name } ` ,
42+ postError ,
43+ ) ;
44+ return { ...author , postCount : 0 } ;
45+ }
46+ } ) ,
47+ ) ;
48+
49+ setAuthors ( authorsWithData ) ;
50+ } catch ( err ) {
51+ console . error ( 'Error loading authors:' , err ) ;
52+ setError ( 'Failed to load author information' ) ;
53+ } finally {
54+ setIsLoading ( false ) ;
55+ }
56+ } ;
57+
58+ loadAllAuthors ( ) ;
59+ } , [ ] ) ;
60+
61+ const handleAuthorClick = ( slug : string ) => {
62+ navigate ( `/authors/${ slug } ` ) ;
63+ } ;
64+
65+ const renderContent = ( ) => {
66+ if ( isLoading ) {
67+ return (
68+ < div className = "container mx-auto px-4 py-16 flex justify-center items-center min-h-[60vh]" >
69+ < div className = "flex flex-col items-center" >
70+ < div className = "animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-blue-600 mb-4" > </ div >
71+ < p className = "text-gray-600" > Loading authors...</ p >
72+ </ div >
73+ </ div >
74+ ) ;
75+ }
76+
77+ if ( error ) {
78+ return (
79+ < div className = "container mx-auto px-4 py-16 text-center min-h-[60vh] flex flex-col justify-center" >
80+ < h1 className = "text-3xl font-bold mb-4 text-red-600" >
81+ Something went wrong
82+ </ h1 >
83+ < p className = "mb-8 text-gray-600" > { error } </ p >
84+ </ div >
85+ ) ;
86+ }
87+
88+ if ( authors . length === 0 ) {
89+ return (
90+ < div className = "container mx-auto px-4 py-16 text-center min-h-[60vh] flex flex-col justify-center" >
91+ < h1 className = "text-3xl font-bold mb-4 text-blue-600" > No Authors</ h1 >
92+ < p className = "mb-8 text-gray-600" >
93+ There are no authors to display at this time.
94+ </ p >
95+ </ div >
96+ ) ;
97+ }
98+
99+ return (
100+ < div className = "space-y-8" >
101+ { authors . map ( ( author , index ) => (
102+ < motion . div
103+ key = { author . slug }
104+ onClick = { ( ) => handleAuthorClick ( author . slug ) }
105+ className = "cursor-pointer group"
106+ initial = { { opacity : 0 , y : 20 } }
107+ animate = { { opacity : 1 , y : 0 } }
108+ transition = { { duration : 0.5 , delay : index * 0.1 } }
109+ whileHover = { { scale : 1.02 } }
110+ >
111+ < div className = "bg-white dark:bg-gray-800 rounded-2xl shadow-xl dark:shadow-2xl dark:shadow-black/20 p-4 sm:p-6 lg:p-8 transition-all duration-300 group-hover:shadow-blue-200 dark:group-hover:shadow-blue-500/30" >
112+ < div className = "flex flex-col sm:flex-row items-center sm:items-start gap-6 lg:gap-8" >
113+ { /* Avatar */ }
114+ < div className = "flex-shrink-0" >
115+ { author . avatar ? (
116+ < img
117+ src = { author . avatar }
118+ alt = { author . name }
119+ className = "w-24 h-24 sm:w-28 sm:h-28 lg:w-32 lg:h-32 rounded-full object-cover border-4 border-blue-100 dark:border-blue-900"
120+ />
121+ ) : (
122+ < div className = "w-24 h-24 sm:w-28 sm:h-28 lg:w-32 lg:h-32 bg-blue-100 dark:bg-gray-700/50 rounded-full flex items-center justify-center" >
123+ < User className = "w-12 h-12 sm:w-14 sm:h-14 lg:w-16 lg:h-16 text-blue-600 dark:text-blue-400" />
124+ </ div >
125+ ) }
126+ </ div >
127+
128+ { /* Author Info */ }
129+ < div className = "flex-1 text-center sm:text-left" >
130+ < h1 className = "text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900 dark:text-white mb-2 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors" >
131+ { author . name }
132+ </ h1 >
133+ < div className = "flex flex-col sm:flex-row sm:items-center gap-2 mb-3" >
134+ < span className = "text-lg lg:text-xl text-blue-600 dark:text-blue-400 font-medium" >
135+ { author . title }
136+ </ span >
137+ { author . organization && (
138+ < >
139+ < span className = "hidden sm:inline text-gray-400 dark:text-gray-500" >
140+ at
141+ </ span >
142+ < div className = "flex items-center justify-center sm:justify-start gap-1 text-gray-700 dark:text-gray-300" >
143+ < Building className = "w-4 h-4" />
144+ < span className = "font-medium" >
145+ { author . organization }
146+ </ span >
147+ </ div >
148+ </ >
149+ ) }
150+ </ div >
151+ < p className = "text-gray-600 dark:text-gray-300 text-base lg:text-lg mb-4 max-w-2xl line-clamp-2" >
152+ { author . description }
153+ </ p >
154+
155+ { /* Quick Stats */ }
156+ < div className = "flex flex-wrap justify-center sm:justify-start gap-4 text-sm text-gray-600 dark:text-gray-300" >
157+ < div className = "flex items-center gap-1 bg-blue-50 dark:bg-blue-900/30 px-3 py-1 rounded-full" >
158+ < BookOpen className = "w-4 h-4" />
159+ < span >
160+ { author . postCount } { ' ' }
161+ { author . postCount === 1 ? 'Article' : 'Articles' }
162+ </ span >
163+ </ div >
164+ { author . organization && (
165+ < div className = "flex items-center gap-1 bg-gray-50 dark:bg-gray-700/50 px-3 py-1 rounded-full" >
166+ < Building className = "w-4 h-4" />
167+ < span > { author . organization } </ span >
168+ </ div >
169+ ) }
170+ </ div >
171+ </ div >
172+ </ div >
173+ </ div >
174+ </ motion . div >
175+ ) ) }
176+ </ div >
177+ ) ;
178+ } ;
179+
180+ return (
181+ < >
182+ < Header />
183+ < div className = "min-h-screen bg-gradient-to-br from-blue-50 via-white to-green-50 dark:from-gray-900 dark:via-gray-900 dark:to-gray-800" >
184+ < div className = "container mx-auto px-4 py-12 max-w-5xl" >
185+ < motion . h1
186+ className = "text-4xl sm:text-5xl font-bold text-center mb-10 text-gray-900 dark:text-white"
187+ initial = { { opacity : 0 , y : - 20 } }
188+ animate = { { opacity : 1 , y : 0 } }
189+ transition = { { duration : 0.5 } }
190+ >
191+ Meet Our Authors
192+ </ motion . h1 >
193+ { renderContent ( ) }
194+ </ div >
195+ </ div >
196+ < Footer />
197+ </ >
198+ ) ;
199+ } ;
200+
201+ export default AuthorsPage ;
0 commit comments