@@ -24,21 +24,104 @@ interface Contributor {
2424 html_url : string ;
2525}
2626
27+ interface FetchError {
28+ message : string ;
29+ isRateLimited : boolean ;
30+ statusCode ?: number ;
31+ }
32+
33+ // Custom error class for Contributors fetch errors
34+ class ContributorsError extends Error {
35+ constructor (
36+ message : string ,
37+ public isRateLimited = false ,
38+ public statusCode ?: number
39+ ) {
40+ super ( message ) ;
41+ this . name = "ContributorsError" ;
42+ }
43+ }
44+
45+ // Type guard to validate if data is Contributor[]
46+ const isContributorArray = ( data : unknown ) : data is Contributor [ ] => {
47+ if ( ! Array . isArray ( data ) ) return false ;
48+ return data . every ( ( item ) => {
49+ if ( typeof item !== "object" || item === null ) return false ;
50+
51+ // Validate all required fields with correct types
52+ return (
53+ typeof item . id === "number" &&
54+ typeof item . login === "string" &&
55+ typeof item . avatar_url === "string" &&
56+ typeof item . contributions === "number" &&
57+ typeof item . html_url === "string"
58+ ) ;
59+ } ) ;
60+ } ;
61+
2762const ContributorsPage = ( ) => {
2863 const [ contributors , setContributors ] = useState < Contributor [ ] > ( [ ] ) ;
2964 const [ loading , setLoading ] = useState < boolean > ( true ) ;
30- const [ error , setError ] = useState < string | null > ( null ) ;
65+ const [ error , setError ] = useState < FetchError | null > ( null ) ;
3166
3267 // Fetch contributors from GitHub API
3368 useEffect ( ( ) => {
3469 const fetchContributors = async ( ) => {
3570 try {
71+ setLoading ( true ) ;
72+ setError ( null ) ;
73+
3674 const response = await axios . get ( GITHUB_REPO_CONTRIBUTORS_URL , {
3775 withCredentials : false ,
76+ timeout : 10000 ,
3877 } ) ;
78+
79+ // ✅ Validate response structure matches Contributor[]
80+ if ( ! isContributorArray ( response . data ) ) {
81+ throw new ContributorsError (
82+ "Invalid API response structure. Expected array of contributors." ,
83+ false
84+ ) ;
85+ }
86+
3987 setContributors ( response . data ) ;
40- } catch {
41- setError ( "Failed to fetch contributors. Please try again later." ) ;
88+ } catch ( err ) {
89+ const fetchError : FetchError = {
90+ message : "Failed to fetch contributors. Please try again later." ,
91+ isRateLimited : false ,
92+ } ;
93+
94+ // Handle ContributorsError instances
95+ if ( err instanceof ContributorsError ) {
96+ fetchError . message = err . message ;
97+ fetchError . isRateLimited = err . isRateLimited ;
98+ fetchError . statusCode = err . statusCode ;
99+ } else if ( axios . isAxiosError ( err ) ) {
100+ // Handle Axios errors
101+ if ( err . response ?. status === 403 ) {
102+ fetchError . message =
103+ "GitHub API rate limit exceeded. Try again later." ;
104+ fetchError . isRateLimited = true ;
105+ fetchError . statusCode = 403 ;
106+ } else if ( err . response ?. status === 404 ) {
107+ fetchError . message = "Repository not found." ;
108+ fetchError . statusCode = 404 ;
109+ } else if ( err . code === "ECONNABORTED" ) {
110+ fetchError . message = "Request timeout. Server took too long to respond." ;
111+ fetchError . statusCode = 408 ;
112+ } else if ( err . response ?. status ) {
113+ fetchError . message = `HTTP ${ err . response . status } : Failed to fetch contributors` ;
114+ fetchError . statusCode = err . response . status ;
115+ } else if ( err . message ) {
116+ fetchError . message = err . message ;
117+ }
118+ } else if ( err instanceof Error ) {
119+ fetchError . message = err . message || fetchError . message ;
120+ }
121+
122+ setError ( fetchError ) ;
123+ console . error ( "Contributors fetch error:" , fetchError ) ;
124+ setContributors ( [ ] ) ;
42125 } finally {
43126 setLoading ( false ) ;
44127 }
@@ -57,8 +140,27 @@ const ContributorsPage = () => {
57140
58141 if ( error ) {
59142 return (
60- < Box sx = { { mt : 4 } } >
61- < Alert severity = "error" > { error } </ Alert >
143+ < Box sx = { { mt : 4 , mx : 2 } } >
144+ < Alert severity = "error" sx = { { mb : 2 } } >
145+ < Typography variant = "body2" sx = { { fontWeight : "bold" } } >
146+ ⚠️ { error . message }
147+ </ Typography >
148+ { error . isRateLimited && (
149+ < Typography variant = "caption" sx = { { display : "block" , mt : 1 } } >
150+ You've hit GitHub's API rate limit. The limit resets in 1 hour.
151+ </ Typography >
152+ ) }
153+ { error . statusCode === 404 && (
154+ < Typography variant = "caption" sx = { { display : "block" , mt : 1 } } >
155+ Please verify the repository exists and is accessible.
156+ </ Typography >
157+ ) }
158+ { error . statusCode === 408 && (
159+ < Typography variant = "caption" sx = { { display : "block" , mt : 1 } } >
160+ The server took too long to respond. Please try again.
161+ </ Typography >
162+ ) }
163+ </ Alert >
62164 </ Box >
63165 ) ;
64166 }
0 commit comments