├── .gitignore ├── .netlify └── state.json ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.css ├── App.js ├── components │ ├── layout │ │ ├── Alert.js │ │ ├── Navbar.js │ │ ├── Spinner.js │ │ └── spinner.gif │ ├── pages │ │ ├── About.js │ │ ├── Home.js │ │ └── NotFound.js │ ├── repos │ │ ├── RepoItem.js │ │ └── Repos.js │ └── users │ │ ├── Search.js │ │ ├── User.js │ │ ├── UserItem.js │ │ └── Users.js ├── context │ ├── alert │ │ ├── AlertState.js │ │ ├── alertContext.js │ │ └── alertReducer.js │ ├── github │ │ ├── GithubState.js │ │ ├── githubContext.js │ │ └── githubReducer.js │ └── types.js └── index.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "19c9052a-732b-4800-bab2-d9ed70d76409" 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Github Finder 2 | 3 | > React app to search Github profiles. This app uses the Context API along with the useContext and useReducer hooks for state management and is part of the "Modern React Front To Back" Udemy course 4 | 5 | ## Usage 6 | 7 | ### `npm install` 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.
12 | Open [http://localhost:3000](http://localhost:3000) 13 | 14 | ### `npm run build` 15 | 16 | Builds the app for production to the `build` folder.
17 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "CI='' npm run build" 3 | publish="build" 4 | [[redirects]] 5 | from = "/*" 6 | to = "/index.html" 7 | status = 200 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-finder", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.1", 7 | "react": "^16.8.6", 8 | "react-dom": "^16.8.6", 9 | "react-router-dom": "^5.0.0", 10 | "react-scripts": "3.0.1" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/github-finder/6248ac6befc0a78d0a49f18a9805738108d6b06a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | Github Finder 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto'); 2 | 3 | /* Global Styles */ 4 | :root { 5 | --primary-color: #dc3545; 6 | --dark-color: #333333; 7 | --light-color: #f4f4f4; 8 | --danger-color: #dc3545; 9 | --success-color: #28a745; 10 | } 11 | 12 | * { 13 | box-sizing: border-box; 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | body { 19 | font-family: 'Roboto', sans-serif; 20 | font-size: 1rem; 21 | line-height: 1.6; 22 | background-color: #fff; 23 | color: #333; 24 | } 25 | 26 | a { 27 | color: var(--primary-color); 28 | text-decoration: none; 29 | } 30 | 31 | a:hover { 32 | color: #666; 33 | } 34 | 35 | ul { 36 | list-style: none; 37 | } 38 | 39 | img { 40 | width: 100%; 41 | } 42 | 43 | /* Utilities */ 44 | .container { 45 | max-width: 1100px; 46 | margin: auto; 47 | overflow: hidden; 48 | padding: 0 2rem; 49 | } 50 | 51 | /* Text Styles*/ 52 | .x-large { 53 | font-size: 4rem; 54 | line-height: 1.2; 55 | margin-bottom: 1rem; 56 | } 57 | 58 | .large { 59 | font-size: 3rem; 60 | line-height: 1.2; 61 | margin-bottom: 1rem; 62 | } 63 | 64 | .lead { 65 | font-size: 1.5rem; 66 | margin-bottom: 1rem; 67 | } 68 | 69 | .text-center { 70 | text-align: center; 71 | } 72 | 73 | .text-primary { 74 | color: var(--primary-color); 75 | } 76 | 77 | .text-dark { 78 | color: var(--dark-color); 79 | } 80 | 81 | .text-success { 82 | color: var(--success-color); 83 | } 84 | 85 | .text-danger { 86 | color: var(--danger-color); 87 | } 88 | 89 | .text-center { 90 | text-align: center; 91 | } 92 | 93 | .text-right { 94 | text-align: right; 95 | } 96 | 97 | .text-left { 98 | text-align: left; 99 | } 100 | 101 | /* Center All */ 102 | .all-center { 103 | display: flex; 104 | flex-direction: column; 105 | width: 100%; 106 | margin: auto; 107 | justify-content: center; 108 | align-items: center; 109 | text-align: center; 110 | } 111 | 112 | /* Cards */ 113 | .card { 114 | padding: 1rem; 115 | border: #ccc 1px dotted; 116 | margin: 0.7rem 0; 117 | } 118 | 119 | /* List */ 120 | .list { 121 | margin: 0.5rem 0; 122 | } 123 | 124 | .list li { 125 | padding-bottom: 0.3rem; 126 | } 127 | 128 | /* Padding */ 129 | .p { 130 | padding: 0.5rem; 131 | } 132 | .p-1 { 133 | padding: 1rem; 134 | } 135 | .p-2 { 136 | padding: 2rem; 137 | } 138 | .p-3 { 139 | padding: 3rem; 140 | } 141 | .py { 142 | padding: 0.5rem 0; 143 | } 144 | .py-1 { 145 | padding: 1rem 0; 146 | } 147 | .py-2 { 148 | padding: 2rem 0; 149 | } 150 | .py-3 { 151 | padding: 3rem 0; 152 | } 153 | 154 | /* Margin */ 155 | .m { 156 | margin: 0.5rem; 157 | } 158 | .m-1 { 159 | margin: 1rem; 160 | } 161 | .m-2 { 162 | margin: 2rem; 163 | } 164 | .m-3 { 165 | margin: 3rem; 166 | } 167 | .my { 168 | margin: 0.5rem 0; 169 | } 170 | .my-1 { 171 | margin: 1rem 0; 172 | } 173 | .my-2 { 174 | margin: 2rem 0; 175 | } 176 | .my-3 { 177 | margin: 3rem 0; 178 | } 179 | 180 | /* Grid */ 181 | .grid-2 { 182 | display: grid; 183 | grid-template-columns: repeat(2, 1fr); 184 | grid-gap: 1rem; 185 | } 186 | 187 | .grid-3 { 188 | display: grid; 189 | grid-template-columns: repeat(3, 1fr); 190 | grid-gap: 1rem; 191 | } 192 | 193 | .grid-4 { 194 | display: grid; 195 | grid-template-columns: repeat(4, 1fr); 196 | grid-gap: 1rem; 197 | } 198 | 199 | .btn { 200 | display: inline-block; 201 | background: var(--light-color); 202 | color: #333; 203 | padding: 0.4rem 1.3rem; 204 | font-size: 1rem; 205 | border: none; 206 | cursor: pointer; 207 | margin-right: 0.5rem; 208 | transition: opacity 0.2s ease-in; 209 | outline: none; 210 | } 211 | 212 | .btn-link { 213 | background: none; 214 | padding: 0; 215 | margin: 0; 216 | } 217 | 218 | .btn-block { 219 | display: block; 220 | width: 100%; 221 | } 222 | 223 | .btn-sm { 224 | font-size: 0.8rem; 225 | padding: 0.3rem 1rem; 226 | margin-right: 0.2rem; 227 | } 228 | 229 | .badge { 230 | display: inline-block; 231 | font-size: 0.8rem; 232 | padding: 0.2rem 0.7rem; 233 | text-align: center; 234 | margin: 0.3rem; 235 | background: var(--light-color); 236 | color: #333; 237 | border-radius: 5px; 238 | } 239 | 240 | .alert { 241 | padding: 0.7rem; 242 | margin: 1rem 0; 243 | opacity: 0.9; 244 | background: var(--light-color); 245 | color: #333; 246 | } 247 | 248 | .btn-primary, 249 | .bg-primary, 250 | .badge-primary, 251 | .alert-primary { 252 | background: var(--primary-color); 253 | color: #fff; 254 | } 255 | 256 | .btn-light, 257 | .bg-light, 258 | .badge-light, 259 | .alert-light { 260 | background: var(--light-color); 261 | color: #333; 262 | } 263 | 264 | .btn-dark, 265 | .bg-dark, 266 | .badge-dark, 267 | .alert-dark { 268 | background: var(--dark-color); 269 | color: #fff; 270 | } 271 | 272 | .btn-danger, 273 | .bg-danger, 274 | .badge-danger, 275 | .alert-danger { 276 | background: var(--danger-color); 277 | color: #fff; 278 | } 279 | 280 | .btn-success, 281 | .bg-success, 282 | .badge-success, 283 | .alert-success { 284 | background: var(--success-color); 285 | color: #fff; 286 | } 287 | 288 | .btn-white, 289 | .bg-white, 290 | .badge-white, 291 | .alert-white { 292 | background: #fff; 293 | color: #333; 294 | border: #ccc solid 1px; 295 | } 296 | 297 | .btn:hover { 298 | opacity: 0.8; 299 | } 300 | 301 | .bg-light, 302 | .badge-light { 303 | border: #ccc solid 1px; 304 | } 305 | 306 | .round-img { 307 | border-radius: 50%; 308 | } 309 | 310 | /* Forms */ 311 | input { 312 | margin: 1.2rem 0; 313 | } 314 | 315 | .form-text { 316 | display: block; 317 | margin-top: 0.3rem; 318 | color: #888; 319 | } 320 | 321 | input[type='text'], 322 | input[type='email'], 323 | input[type='password'], 324 | input[type='date'], 325 | select, 326 | textarea { 327 | display: block; 328 | width: 100%; 329 | padding: 0.4rem; 330 | font-size: 1.2rem; 331 | border: 1px solid #ccc; 332 | } 333 | 334 | input[type='submit'], 335 | button { 336 | font: inherit; 337 | } 338 | 339 | table th, 340 | table td { 341 | padding: 1rem; 342 | text-align: left; 343 | } 344 | 345 | table th { 346 | background: var(--light-color); 347 | } 348 | 349 | /* Navbar */ 350 | .navbar { 351 | display: flex; 352 | justify-content: space-between; 353 | align-items: center; 354 | padding: 0.7rem 2rem; 355 | z-index: 1; 356 | width: 100%; 357 | opacity: 0.9; 358 | margin-bottom: 1rem; 359 | } 360 | 361 | .navbar ul { 362 | display: flex; 363 | } 364 | 365 | .navbar a { 366 | color: #fff; 367 | padding: 0.45rem; 368 | margin: 0 0.25rem; 369 | } 370 | 371 | .navbar a:hover { 372 | color: var(--light-color); 373 | } 374 | 375 | .navbar .welcome span { 376 | margin-right: 0.6rem; 377 | } 378 | 379 | /* Mobile Styles */ 380 | @media (max-width: 700px) { 381 | .hide-sm { 382 | display: none; 383 | } 384 | 385 | .grid-2, 386 | .grid-3, 387 | .grid-4 { 388 | grid-template-columns: 1fr; 389 | } 390 | 391 | /* Text Styles */ 392 | .x-large { 393 | font-size: 3rem; 394 | } 395 | 396 | .large { 397 | font-size: 2rem; 398 | } 399 | 400 | .lead { 401 | font-size: 1rem; 402 | } 403 | 404 | /* Navbar */ 405 | .navbar { 406 | display: block; 407 | text-align: center; 408 | } 409 | 410 | .navbar ul { 411 | text-align: center; 412 | justify-content: center; 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'; 3 | import Navbar from './components/layout/Navbar'; 4 | import User from './components/users/User'; 5 | import Alert from './components/layout/Alert'; 6 | import Home from './components/pages/Home'; 7 | import About from './components/pages/About'; 8 | import NotFound from './components/pages/NotFound'; 9 | 10 | import GithubState from './context/github/GithubState'; 11 | import AlertState from './context/alert/AlertState'; 12 | 13 | import './App.css'; 14 | 15 | const App = () => { 16 | return ( 17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default App; 39 | -------------------------------------------------------------------------------- /src/components/layout/Alert.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import AlertContext from '../../context/alert/alertContext'; 3 | 4 | const Alert = () => { 5 | const alertContext = useContext(AlertContext); 6 | 7 | const { alert } = alertContext; 8 | 9 | return ( 10 | alert !== null && ( 11 |
12 | {alert.msg} 13 |
14 | ) 15 | ); 16 | }; 17 | 18 | export default Alert; 19 | -------------------------------------------------------------------------------- /src/components/layout/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const Navbar = ({ icon, title }) => { 6 | return ( 7 | 20 | ); 21 | }; 22 | 23 | Navbar.defaultProps = { 24 | title: 'Github Finder', 25 | icon: 'fab fa-github' 26 | }; 27 | 28 | Navbar.propTypes = { 29 | title: PropTypes.string.isRequired, 30 | icon: PropTypes.string.isRequired 31 | }; 32 | 33 | export default Navbar; 34 | -------------------------------------------------------------------------------- /src/components/layout/Spinner.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import spinner from './spinner.gif'; 3 | 4 | const Spinner = () => 5 | Loading... 6 | 7 | 8 | export default Spinner 9 | -------------------------------------------------------------------------------- /src/components/layout/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradtraversy/github-finder/6248ac6befc0a78d0a49f18a9805738108d6b06a/src/components/layout/spinner.gif -------------------------------------------------------------------------------- /src/components/pages/About.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | 3 | const About = () => { 4 | return ( 5 | 6 |

About This App

7 |

App to search Github users

8 |

Version: 1.0.0

9 |
10 | ); 11 | }; 12 | 13 | export default About; 14 | -------------------------------------------------------------------------------- /src/components/pages/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import Search from '../users/Search'; 3 | import Users from '../users/Users'; 4 | 5 | const Home = () => ( 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default Home; 13 | -------------------------------------------------------------------------------- /src/components/pages/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NotFound = () => { 4 | return ( 5 |
6 |

Not Found

7 |

The page you are looking for does not exist...

8 |
9 | ); 10 | }; 11 | 12 | export default NotFound; 13 | -------------------------------------------------------------------------------- /src/components/repos/RepoItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const RepoItem = ({ repo }) => { 5 | return ( 6 |
7 |

8 | {repo.name} 9 |

10 |
11 | ); 12 | }; 13 | 14 | RepoItem.propTypes = { 15 | repo: PropTypes.object.isRequired 16 | }; 17 | 18 | export default RepoItem; 19 | -------------------------------------------------------------------------------- /src/components/repos/Repos.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import RepoItem from './RepoItem'; 4 | 5 | const Repos = ({ repos }) => { 6 | return repos.map(repo => ); 7 | }; 8 | 9 | Repos.propTypes = { 10 | repos: PropTypes.array.isRequired 11 | }; 12 | 13 | export default Repos; 14 | -------------------------------------------------------------------------------- /src/components/users/Search.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import GithubContext from '../../context/github/githubContext'; 3 | import AlertContext from '../../context/alert/alertContext'; 4 | 5 | const Search = () => { 6 | const githubContext = useContext(GithubContext); 7 | const alertContext = useContext(AlertContext); 8 | 9 | const [text, setText] = useState(''); 10 | 11 | const onSubmit = e => { 12 | e.preventDefault(); 13 | if (text === '') { 14 | alertContext.setAlert('Please enter something', 'light'); 15 | } else { 16 | githubContext.searchUsers(text); 17 | setText(''); 18 | } 19 | }; 20 | 21 | const onChange = e => setText(e.target.value); 22 | 23 | return ( 24 |
25 |
26 | 33 | 38 |
39 | {githubContext.users.length > 0 && ( 40 | 46 | )} 47 |
48 | ); 49 | }; 50 | 51 | export default Search; 52 | -------------------------------------------------------------------------------- /src/components/users/User.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useEffect, useContext } from 'react'; 2 | import Spinner from '../layout/Spinner'; 3 | import Repos from '../repos/Repos'; 4 | import { Link } from 'react-router-dom'; 5 | import GithubContext from '../../context/github/githubContext'; 6 | 7 | const User = ({ match }) => { 8 | const githubContext = useContext(GithubContext); 9 | 10 | const { getUser, loading, user, repos, getUserRepos } = githubContext; 11 | 12 | useEffect(() => { 13 | getUser(match.params.login); 14 | getUserRepos(match.params.login); 15 | // eslint-disable-next-line 16 | }, []); 17 | 18 | const { 19 | name, 20 | company, 21 | avatar_url, 22 | location, 23 | bio, 24 | blog, 25 | login, 26 | html_url, 27 | followers, 28 | following, 29 | public_repos, 30 | public_gists, 31 | hireable 32 | } = user; 33 | 34 | if (loading) return ; 35 | 36 | return ( 37 | 38 | 39 | Back To Search 40 | 41 | Hireable:{' '} 42 | {hireable ? ( 43 | 44 | ) : ( 45 | 46 | )} 47 |
48 |
49 | 55 |

{name}

56 |

Location: {location}

57 |
58 |
59 | {bio && ( 60 | 61 |

Bio

62 |

{bio}

63 |
64 | )} 65 | 66 | Visit Github Profile 67 | 68 |
    69 |
  • 70 | {login && ( 71 | 72 | Username: {login} 73 | 74 | )} 75 |
  • 76 | 77 |
  • 78 | {company && ( 79 | 80 | Company: {company} 81 | 82 | )} 83 |
  • 84 | 85 |
  • 86 | {blog && ( 87 | 88 | Website: {blog} 89 | 90 | )} 91 |
  • 92 |
93 |
94 |
95 |
96 |
Followers: {followers}
97 |
Following: {following}
98 |
Public Repos: {public_repos}
99 |
Public Gists: {public_gists}
100 |
101 | 102 |
103 | ); 104 | }; 105 | 106 | export default User; 107 | -------------------------------------------------------------------------------- /src/components/users/UserItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const UserItem = ({ user: { login, avatar_url, html_url } }) => { 6 | return ( 7 |
8 | 14 |

{login}

15 | 16 |
17 | 18 | More 19 | 20 |
21 |
22 | ); 23 | }; 24 | 25 | UserItem.propTypes = { 26 | user: PropTypes.object.isRequired 27 | }; 28 | 29 | export default UserItem; 30 | -------------------------------------------------------------------------------- /src/components/users/Users.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import UserItem from './UserItem'; 3 | import Spinner from '../layout/Spinner'; 4 | import GithubContext from '../../context/github/githubContext'; 5 | 6 | const Users = () => { 7 | const githubContext = useContext(GithubContext); 8 | 9 | const { loading, users } = githubContext; 10 | 11 | if (loading) { 12 | return ; 13 | } else { 14 | return ( 15 |
16 | {users.map(user => ( 17 | 18 | ))} 19 |
20 | ); 21 | } 22 | }; 23 | 24 | const userStyle = { 25 | display: 'grid', 26 | gridTemplateColumns: 'repeat(3, 1fr)', 27 | gridGap: '1rem' 28 | }; 29 | 30 | export default Users; 31 | -------------------------------------------------------------------------------- /src/context/alert/AlertState.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from 'react'; 2 | import AlertContext from './alertContext'; 3 | import AlertReducer from './alertReducer'; 4 | import { SET_ALERT, REMOVE_ALERT } from '../types'; 5 | 6 | const AlertState = props => { 7 | const initialState = null; 8 | 9 | const [state, dispatch] = useReducer(AlertReducer, initialState); 10 | 11 | // Set Alert 12 | const setAlert = (msg, type) => { 13 | dispatch({ 14 | type: SET_ALERT, 15 | payload: { msg, type } 16 | }); 17 | 18 | setTimeout(() => dispatch({ type: REMOVE_ALERT }), 5000); 19 | }; 20 | 21 | return ( 22 | 28 | {props.children} 29 | 30 | ); 31 | }; 32 | 33 | export default AlertState; 34 | -------------------------------------------------------------------------------- /src/context/alert/alertContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const alertContext = createContext(); 4 | 5 | export default alertContext; 6 | -------------------------------------------------------------------------------- /src/context/alert/alertReducer.js: -------------------------------------------------------------------------------- 1 | import { SET_ALERT, REMOVE_ALERT } from '../types'; 2 | 3 | export default (state, action) => { 4 | switch (action.type) { 5 | case SET_ALERT: 6 | return action.payload; 7 | case REMOVE_ALERT: 8 | return null; 9 | default: 10 | return state; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/context/github/GithubState.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from 'react'; 2 | import axios from 'axios'; 3 | import GithubContext from './githubContext'; 4 | import GithubReducer from './githubReducer'; 5 | import { 6 | SEARCH_USERS, 7 | SET_LOADING, 8 | CLEAR_USERS, 9 | GET_USER, 10 | GET_REPOS 11 | } from '../types'; 12 | 13 | let githubClientId; 14 | let githubClientSecret; 15 | 16 | if (process.env.NODE_ENV !== 'production') { 17 | githubClientId = process.env.REACT_APP_GITHUB_CLIENT_ID; 18 | githubClientSecret = process.env.REACT_APP_GITHUB_CLIENT_SECRET; 19 | } else { 20 | githubClientId = process.env.GITHUB_CLIENT_ID; 21 | githubClientSecret = process.env.GITHUB_CLIENT_SECRET; 22 | } 23 | 24 | const GithubState = props => { 25 | const initialState = { 26 | users: [], 27 | user: {}, 28 | repos: [], 29 | loading: false 30 | }; 31 | 32 | const [state, dispatch] = useReducer(GithubReducer, initialState); 33 | 34 | // Search Users 35 | const searchUsers = async text => { 36 | setLoading(); 37 | 38 | const res = await axios.get( 39 | `https://api.github.com/search/users?q=${text}&client_id=${githubClientId}&client_secret=${githubClientSecret}` 40 | ); 41 | 42 | dispatch({ 43 | type: SEARCH_USERS, 44 | payload: res.data.items 45 | }); 46 | }; 47 | 48 | // Get User 49 | const getUser = async username => { 50 | setLoading(); 51 | 52 | const res = await axios.get( 53 | `https://api.github.com/users/${username}?client_id=${githubClientId}&client_secret=${githubClientSecret}` 54 | ); 55 | 56 | dispatch({ 57 | type: GET_USER, 58 | payload: res.data 59 | }); 60 | }; 61 | 62 | // Get Repos 63 | const getUserRepos = async username => { 64 | setLoading(); 65 | 66 | const res = await axios.get( 67 | `https://api.github.com/users/${username}/repos?per_page=5&sort=created:asc&client_id=${githubClientId}&client_secret=${githubClientSecret}` 68 | ); 69 | 70 | dispatch({ 71 | type: GET_REPOS, 72 | payload: res.data 73 | }); 74 | }; 75 | 76 | // Clear Users 77 | const clearUsers = () => dispatch({ type: CLEAR_USERS }); 78 | 79 | // Set Loading 80 | const setLoading = () => dispatch({ type: SET_LOADING }); 81 | 82 | return ( 83 | 95 | {props.children} 96 | 97 | ); 98 | }; 99 | 100 | export default GithubState; 101 | -------------------------------------------------------------------------------- /src/context/github/githubContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const githubContext = createContext(); 4 | 5 | export default githubContext; 6 | -------------------------------------------------------------------------------- /src/context/github/githubReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | SEARCH_USERS, 3 | SET_LOADING, 4 | CLEAR_USERS, 5 | GET_USER, 6 | GET_REPOS 7 | } from '../types'; 8 | 9 | export default (state, action) => { 10 | switch (action.type) { 11 | case SEARCH_USERS: 12 | return { 13 | ...state, 14 | users: action.payload, 15 | loading: false 16 | }; 17 | case GET_USER: 18 | return { 19 | ...state, 20 | user: action.payload, 21 | loading: false 22 | }; 23 | case CLEAR_USERS: 24 | return { 25 | ...state, 26 | users: [], 27 | loading: false 28 | }; 29 | case GET_REPOS: { 30 | return { 31 | ...state, 32 | repos: action.payload, 33 | loading: false 34 | }; 35 | } 36 | case SET_LOADING: 37 | return { 38 | ...state, 39 | loading: true 40 | }; 41 | default: 42 | return state; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/context/types.js: -------------------------------------------------------------------------------- 1 | export const SEARCH_USERS = 'SEARCH_USERS'; 2 | export const GET_USER = 'GET_USER'; 3 | export const CLEAR_USERS = 'CLEAR_USERS'; 4 | export const GET_REPOS = 'GET_REPOS'; 5 | export const SET_LOADING = 'SET_LOADING'; 6 | export const SET_ALERT = 'SET_ALERT'; 7 | export const REMOVE_ALERT = 'REMOVE_ALERT'; 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | --------------------------------------------------------------------------------