1- import React , { useEffect , useState } from "react" ;
2- import { Octokit } from "@octokit/rest" ;
3- import "./contributors.css" ;
1+ import React , { useEffect , useMemo , useState } from "react" ;
42import Translate from "@docusaurus/Translate" ;
3+ import {
4+ ArrowUpRight ,
5+ GitBranch ,
6+ Github ,
7+ Heart ,
8+ Users ,
9+ } from "lucide-react" ;
10+
11+ import "./contributors.css" ;
512
613type Contributor = {
714 login : string ;
815 contributions : number ;
916 html_url : string ;
1017 avatar_url : string ;
18+ type ?: string ;
1119} ;
1220
13- // per page max 99 items
14- type ContibutorProps = {
15- contributorsPerPage : number ;
16- contributorsIgnore : Array < string > ;
21+ type ContributorProps = {
22+ contributorsPerPage ?: number ;
23+ contributorsIgnore ?: string [ ] ;
1724} ;
1825
19- const defaultIgnoreContributors : string [ ] = [
26+ const DEFAULT_IGNORE_CONTRIBUTORS = [
2027 "restyled-commits" ,
2128 "dependabot" ,
2229 "renovate" ,
2330] ;
24- const ContributorList : React . FC < ContibutorProps > = ( {
31+
32+ const GITHUB_OWNER = "CodeHarborHub" ;
33+ const GITHUB_REPO = "codeharborhub.github.io" ;
34+
35+ const ContributorList : React . FC < ContributorProps > = ( {
2536 contributorsPerPage = 99 ,
26- contributorsIgnore = defaultIgnoreContributors ,
27- } : ContibutorProps ) => {
28- const [ contributors , setContributors ] = useState < Contributor > ( [ ] ) ;
37+ contributorsIgnore = DEFAULT_IGNORE_CONTRIBUTORS ,
38+ } ) => {
39+ const [ contributors , setContributors ] = useState < Contributor [ ] > ( [ ] ) ;
40+ const [ loading , setLoading ] = useState ( true ) ;
41+ const [ error , setError ] = useState ( false ) ;
42+
2943 useEffect ( ( ) => {
44+ const controller = new AbortController ( ) ;
45+
3046 const fetchContributors = async ( ) => {
31- const octokit = new Octokit ( ) ;
3247 try {
33- const response = await octokit . request (
34- `GET /repos/{owner}/{repo}/contributors?per_page=${ contributorsPerPage + contributorsIgnore . length } ` ,
48+ setLoading ( true ) ;
49+ setError ( false ) ;
50+
51+ const perPage = Math . min (
52+ Math . max ( contributorsPerPage + contributorsIgnore . length , 1 ) ,
53+ 100 ,
54+ ) ;
55+
56+ const response = await fetch (
57+ `https://api.github.com/repos/${ GITHUB_OWNER } /${ GITHUB_REPO } /contributors?per_page=${ perPage } ` ,
3558 {
36- owner : "codeharborhub" ,
37- repo : "codeharborhub.github.io" ,
3859 headers : {
60+ Accept : "application/vnd.github+json" ,
3961 "X-GitHub-Api-Version" : "2022-11-28" ,
4062 } ,
63+ signal : controller . signal ,
4164 } ,
4265 ) ;
43- const filteredContributors = response . data . filter (
44- ( contribuidor : Contributor ) =>
45- ! contributorsIgnore . some ( ( element ) =>
46- contribuidor . login . toLowerCase ( ) . includes ( element . toLowerCase ( ) ) ,
47- ) ,
66+
67+ if ( ! response . ok ) {
68+ throw new Error ( `GitHub API error: ${ response . status } ` ) ;
69+ }
70+
71+ const data : Contributor [ ] = await response . json ( ) ;
72+
73+ const ignored = contributorsIgnore . map ( ( login ) =>
74+ login . toLowerCase ( ) ,
4875 ) ;
4976
50- setContributors ( filteredContributors ) ;
51- } catch ( error ) {
52- console . error ( "Error fetching contributors:" , error ) ;
77+ const filtered = data
78+ . filter ( ( contributor ) => {
79+ const login = contributor . login . toLowerCase ( ) ;
80+
81+ return (
82+ ! ignored . includes ( login ) &&
83+ ! ignored . some ( ( ignoredLogin ) =>
84+ login . includes ( ignoredLogin ) ,
85+ )
86+ ) ;
87+ } )
88+ . slice ( 0 , contributorsPerPage ) ;
89+
90+ setContributors ( filtered ) ;
91+ } catch ( fetchError ) {
92+ if (
93+ fetchError instanceof DOMException &&
94+ fetchError . name === "AbortError"
95+ ) {
96+ return ;
97+ }
98+
99+ console . error ( "Failed to fetch CodeHarborHub contributors:" , fetchError ) ;
100+ setError ( true ) ;
101+ } finally {
102+ if ( ! controller . signal . aborted ) {
103+ setLoading ( false ) ;
104+ }
53105 }
54106 } ;
55107
56108 fetchContributors ( ) ;
57- } , [ ] ) ;
109+
110+ return ( ) => {
111+ controller . abort ( ) ;
112+ } ;
113+ } , [ contributorsPerPage , contributorsIgnore ] ) ;
114+
115+ const contributorRows = useMemo ( ( ) => {
116+ const rows : Contributor [ ] [ ] = [ [ ] , [ ] , [ ] ] ;
117+
118+ contributors . forEach ( ( contributor , index ) => {
119+ rows [ index % 3 ] . push ( contributor ) ;
120+ } ) ;
121+
122+ return rows ;
123+ } , [ contributors ] ) ;
124+
125+ const githubContributorsUrl = `https://github.com/${ GITHUB_OWNER } /${ GITHUB_REPO } /graphs/contributors` ;
58126
59127 return (
60- < >
61- < div className = "father-flex-div mb-6" >
62- < p className = "intro" >
63- < Translate
64- id = "contributors.thankYouMessage"
65- description = "Thank you message for contributors"
66- values = { {
67- projectName : (
68- < a href = "https://codeharborhub.github.io" >
69- < i > CodeHarborHub</ i >
70- </ a >
71- ) ,
72- } }
128+ < section
129+ className = "contributors-section"
130+ aria-labelledby = "contributors-title"
131+ >
132+ < div className = "contributors-section__container" >
133+ { /* Decorative background */ }
134+ < div
135+ className = "contributors-section__glow contributors-section__glow--one"
136+ aria-hidden = "true"
137+ />
138+
139+ < div
140+ className = "contributors-section__glow contributors-section__glow--two"
141+ aria-hidden = "true"
142+ />
143+
144+ { /* Header */ }
145+ < header className = "contributors-section__header" >
146+ < div className = "contributors-section__eyebrow" >
147+ < GitBranch size = { 15 } aria-hidden = "true" />
148+ < span > OPEN SOURCE COMMUNITY</ span >
149+ </ div >
150+
151+ < h2
152+ id = "contributors-title"
153+ className = "contributors-section__title"
73154 >
74- { `Our heartfelt thanks to the incredible contributors making {projectName} a premier destination for developers. From code to documentation, your expertise
75- shapes this harbor. Together, we're building a faster, better, and more
76- inclusive open-source ecosystem.` }
77- </ Translate >
78- </ p >
79- < div className = "grid" >
80- { [ 0 , 1 , 2 ] . map ( ( value ) => {
81- const middle = Math . ceil ( contributors . length / 3 ) ;
82- const start = value === 0 ? 0 : middle * value ;
83- const end =
84- value === 0
85- ? middle
86- : value === 1
87- ? middle * 2
88- : contributors . length ;
155+ Built by{ " " }
156+ < span > contributors</ span > ,
157+ powered by community.
158+ </ h2 >
89159
90- return (
91- < div className = "grid-item fade-horizontal" key = { value } >
92- { contributors . slice ( start , end ) . map ( ( contributor ) => (
93- < div className = "circle" >
160+ < p className = "contributors-section__description" >
161+ CodeHarborHub grows because developers, writers, designers,
162+ and open-source contributors come together to build something
163+ useful for everyone.
164+ </ p >
165+ </ header >
166+
167+ { /* Main contributor panel */ }
168+ < div className = "contributors-section__panel" >
169+ < div className = "contributors-section__panel-header" >
170+ < div className = "contributors-section__panel-title" >
171+ < div className = "contributors-section__icon" >
172+ < Users size = { 20 } aria-hidden = "true" />
173+ </ div >
174+
175+ < div >
176+ < h3 > Our contributors</ h3 >
177+
178+ < p >
179+ Every contribution helps make the harbor stronger.
180+ </ p >
181+ </ div >
182+ </ div >
183+
184+ { ! loading && ! error && (
185+ < div className = "contributors-section__count" >
186+ < strong > { contributors . length } </ strong >
187+ < span > featured contributors</ span >
188+ </ div >
189+ ) }
190+ </ div >
191+
192+ { /* Loading */ }
193+ { loading && (
194+ < div
195+ className = "contributors-section__loading"
196+ aria-label = "Loading contributors"
197+ >
198+ { [ ...Array ( 42 ) ] . map ( ( _ , index ) => (
199+ < span
200+ key = { index }
201+ className = "contributors-section__skeleton"
202+ />
203+ ) ) }
204+ </ div >
205+ ) }
206+
207+ { /* Error */ }
208+ { ! loading && error && (
209+ < div className = "contributors-section__error" >
210+ < div className = "contributors-section__error-icon" >
211+ < Github size = { 22 } aria-hidden = "true" />
212+ </ div >
213+
214+ < div >
215+ < strong > Contributors couldn't be loaded.</ strong >
216+ < p >
217+ Please visit our GitHub repository to see the latest
218+ contributor list.
219+ </ p >
220+ </ div >
221+
222+ < a
223+ href = { githubContributorsUrl }
224+ target = "_blank"
225+ rel = "noopener noreferrer"
226+ className = "contributors-section__error-link"
227+ >
228+ View on GitHub
229+ < ArrowUpRight size = { 16 } aria-hidden = "true" />
230+ </ a >
231+ </ div >
232+ ) }
233+
234+ { /* Contributors */ }
235+ { ! loading && ! error && contributors . length > 0 && (
236+ < div className = "contributors-section__avatars" >
237+ { contributorRows . map ( ( row , rowIndex ) => (
238+ < div
239+ className = "contributors-section__avatar-row"
240+ key = { `row-${ rowIndex } ` }
241+ >
242+ { row . map ( ( contributor ) => (
94243 < a
95244 href = { contributor . html_url }
96245 target = "_blank"
246+ rel = "noopener noreferrer"
247+ className = "contributor-avatar"
97248 key = { contributor . login }
249+ title = { `${ contributor . login } · ${ contributor . contributions } contributions` }
250+ aria-label = { `View ${ contributor . login } on GitHub` }
98251 >
99252 < img
100- className = "circle"
101253 src = { contributor . avatar_url }
102- alt = { `${ contributor . login } 's avatar` }
254+ alt = ""
255+ loading = "lazy"
256+ width = "48"
257+ height = "48"
103258 />
259+
260+ < span className = "contributor-avatar__tooltip" >
261+ < strong > { contributor . login } </ strong >
262+ < small >
263+ { contributor . contributions } contributions
264+ </ small >
265+ </ span >
104266 </ a >
105- </ div >
106- ) ) }
107- </ div >
108- ) ;
109- } ) }
267+ ) ) }
268+ </ div >
269+ ) ) }
270+ </ div >
271+ ) }
272+
273+ { /* Empty */ }
274+ { ! loading && ! error && contributors . length === 0 && (
275+ < div className = "contributors-section__empty" >
276+ No contributors are currently available.
277+ </ div >
278+ ) }
279+
280+ { /* Footer */ }
281+ < div className = "contributors-section__panel-footer" >
282+ < div className = "contributors-section__thanks" >
283+ < Heart size = { 16 } aria-hidden = "true" />
284+
285+ < span >
286+ < Translate
287+ id = "contributors.thankYouMessage"
288+ description = "Thank you message for CodeHarborHub contributors"
289+ values = { {
290+ projectName : (
291+ < a
292+ href = "https://codeharborhub.github.io"
293+ target = "_blank"
294+ rel = "noopener noreferrer"
295+ >
296+ CodeHarborHub
297+ </ a >
298+ ) ,
299+ } }
300+ >
301+ {
302+ "Thank you to every contributor helping {projectName} become a better place to learn, build, and collaborate."
303+ }
304+ </ Translate >
305+ </ span >
306+ </ div >
307+
308+ < a
309+ href = { githubContributorsUrl }
310+ target = "_blank"
311+ rel = "noopener noreferrer"
312+ className = "contributors-section__github-button"
313+ >
314+ < Github size = { 17 } aria-hidden = "true" />
315+ < span > View all contributors</ span >
316+ < ArrowUpRight size = { 16 } aria-hidden = "true" />
317+ </ a >
318+ </ div >
110319 </ div >
111320 </ div >
112- </ >
321+ </ section >
113322 ) ;
114323} ;
115324
116- export default ContributorList ;
325+ export default ContributorList ;
0 commit comments