1- import { useState } from 'react' ;
1+ import { useState , useEffect } from 'react' ;
22import Button from 'react-bootstrap/Button' ;
33import Card from 'react-bootstrap/Card' ;
44import ListGroup from 'react-bootstrap/ListGroup' ;
55import Container from 'react-bootstrap/Container' ;
66import Row from 'react-bootstrap/Row' ;
77import Col from 'react-bootstrap/Col' ;
88import Form from 'react-bootstrap/Form' ;
9+ import Spinner from 'react-bootstrap/Spinner' ;
910import Header from './components/Header/Header' ;
1011
1112function App ( ) {
1213 const [ user , setUser ] = useState ( null ) ;
1314 const [ repos , setRepos ] = useState ( [ ] ) ;
1415 const [ searchRepo , setSearchRepo ] = useState ( '' ) ;
16+ const [ loading , setLoading ] = useState ( false ) ;
17+ const [ reposLoading , setReposLoading ] = useState ( false ) ;
1518 const [ error , setError ] = useState ( '' ) ;
19+ const [ lastSearchedUser , setLastSearchedUser ] = useState ( '' ) ;
20+ const [ lastFetchedReposUser , setLastFetchedReposUser ] = useState ( '' ) ;
1621
17- const fetchUser = username => {
18- fetch ( `https://api.github.com/users/${ username } ` )
19- . then ( res => res . json ( ) )
20- . then ( result => {
21- if ( result . message === 'Not Found' ) {
22- setError ( 'User not found' ) ;
23- setUser ( null ) ;
24- setRepos ( [ ] ) ;
25- } else {
26- setError ( '' ) ;
27- setUser ( result ) ;
28- setRepos ( [ ] ) ;
29- }
30- } )
31- . catch ( ( ) => {
32- setError ( 'Something went wrong' ) ;
33- setUser ( null ) ;
34- setRepos ( [ ] ) ;
35- } ) ;
22+ const fetchUser = async username => {
23+ if ( username . toLowerCase ( ) === lastSearchedUser . toLowerCase ( ) ) return ;
24+
25+ setLoading ( true ) ;
26+ setError ( '' ) ;
27+ setUser ( null ) ;
28+ setRepos ( [ ] ) ;
29+ setLastFetchedReposUser ( '' ) ;
30+
31+ try {
32+ const res = await fetch ( `https://api.github.com/users/${ username } ` ) ;
33+ const result = await res . json ( ) ;
34+
35+ if ( result . message === 'Not Found' ) {
36+ setError ( 'User not found' ) ;
37+ } else {
38+ setUser ( result ) ;
39+ setLastSearchedUser ( username ) ;
40+ }
41+ } catch ( err ) {
42+ console . error ( err ) ;
43+ setError ( 'Something went wrong' ) ;
44+ } finally {
45+ setLoading ( false ) ;
46+ }
3647 } ;
3748
38- const fetchRepos = username => {
39- fetch ( `https://api.github.com/users/${ username } /repos` )
40- . then ( res => res . json ( ) )
41- . then ( result => {
42- if ( Array . isArray ( result ) ) {
43- setRepos ( result ) ;
44- } else {
45- setRepos ( [ ] ) ;
46- }
47- } )
48- . catch ( ( ) => setRepos ( [ ] ) ) ;
49+ const fetchRepos = async username => {
50+ if ( username . toLowerCase ( ) === lastFetchedReposUser . toLowerCase ( ) ) return ;
51+
52+ setReposLoading ( true ) ;
53+ try {
54+ const res = await fetch ( `https://api.github.com/users/${ username } /repos` ) ;
55+ const result = await res . json ( ) ;
56+ setRepos ( Array . isArray ( result ) ? result : [ ] ) ;
57+ setLastFetchedReposUser ( username ) ;
58+ } catch ( err ) {
59+ console . error ( err ) ;
60+ setRepos ( [ ] ) ;
61+ } finally {
62+ setReposLoading ( false ) ;
63+ }
4964 } ;
5065
5166 const filteredRepos = repos . filter (
@@ -55,6 +70,10 @@ function App() {
5570 repo . description . toLowerCase ( ) . includes ( searchRepo . toLowerCase ( ) ) )
5671 ) ;
5772
73+ useEffect ( ( ) => {
74+ fetchUser ( 'NJul' ) ;
75+ } , [ ] ) ;
76+
5877 return (
5978 < >
6079 < Header onSearch = { fetchUser } />
@@ -63,37 +82,53 @@ function App() {
6382 < Row className = 'justify-content-center' >
6483 { error && < p className = 'text-danger text-center' > { error } </ p > }
6584
66- { user && (
67- < Col xs = { 12 } sm = { 10 } md = { 6 } lg = { 4 } className = 'mb-4' >
68- < Card className = 'shadow-sm h-100' >
69- < Card . Img variant = 'top' src = { user . avatar_url } />
70- < Card . Body className = 'text-center' >
71- < Card . Title > { user . login } </ Card . Title >
72- < Card . Text >
73- < a
74- href = { user . html_url }
75- target = '_blank'
76- rel = 'noopener noreferrer'
85+ { loading ? (
86+ < div className = 'd-flex justify-content-center align-items-center py-5' >
87+ < Spinner animation = 'border' role = 'status' variant = 'primary' >
88+ < span className = 'visually-hidden' > Loading user ...</ span >
89+ </ Spinner >
90+ </ div >
91+ ) : (
92+ user && (
93+ < Col xs = { 12 } sm = { 10 } md = { 6 } lg = { 4 } className = 'mb-4' >
94+ < Card className = 'shadow-sm h-100' >
95+ < Card . Img variant = 'top' src = { user . avatar_url } />
96+ < Card . Body className = 'text-center' >
97+ < Card . Title > { user . login } </ Card . Title >
98+ < Card . Text >
99+ < a
100+ href = { user . html_url }
101+ target = '_blank'
102+ rel = 'noopener noreferrer'
103+ >
104+ View Profile
105+ </ a >
106+ </ Card . Text >
107+ < Card . Text >
108+ Followers: { user . followers } | Following: { user . following }
109+ </ Card . Text >
110+ < Button
111+ variant = 'primary'
112+ onClick = { ( ) => fetchRepos ( user . login ) }
77113 >
78- View Profile
79- </ a >
80- </ Card . Text >
81- < Card . Text >
82- Followers: { user . followers } | Following: { user . following }
83- </ Card . Text >
84- < Button
85- variant = 'primary'
86- onClick = { ( ) => fetchRepos ( user . login ) }
87- >
88- Show Public Repos
89- </ Button >
90- </ Card . Body >
91- </ Card >
92- </ Col >
114+ Show Public Repos
115+ </ Button >
116+ </ Card . Body >
117+ </ Card >
118+ </ Col >
119+ )
93120 ) }
94121 </ Row >
95122
96- { repos . length > 0 && (
123+ { reposLoading && (
124+ < div className = 'd-flex justify-content-center align-items-center py-4' >
125+ < Spinner animation = 'border' role = 'status' variant = 'secondary' >
126+ < span className = 'visually-hidden' > Loading repositories...</ span >
127+ </ Spinner >
128+ </ div >
129+ ) }
130+
131+ { ! reposLoading && repos . length > 0 && (
97132 < Row className = 'justify-content-center' >
98133 < Col xs = { 12 } sm = { 11 } md = { 10 } lg = { 8 } >
99134 < Card className = 'shadow-sm' >
@@ -103,7 +138,15 @@ function App() {
103138 </ Card . Title >
104139
105140 < Form className = 'mb-3' >
141+ < Form . Label
142+ htmlFor = 'repo-search'
143+ className = 'visually-hidden'
144+ >
145+ Search Repositories
146+ </ Form . Label >
106147 < Form . Control
148+ id = 'repo-search'
149+ name = 'repoSearch'
107150 type = 'text'
108151 placeholder = 'Search repositories ...'
109152 value = { searchRepo }
0 commit comments