├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------