├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .nojekyll ├── 404.html ├── LICENSE ├── README.md ├── build ├── assets │ ├── color.c7a33805ffda0d32bd2a9904c8b02750.png │ ├── fontawesome-webfont.674f50d287a8c48dc19ba404d20fe713.eot │ ├── fontawesome-webfont.912ec66d7572ff821749319396470bde.svg │ ├── fontawesome-webfont.af7ae505a9eed503f8b8e6982036873e.woff2 │ ├── fontawesome-webfont.b06871f281fee6b241d60582ae9369b9.ttf │ ├── fontawesome-webfont.fee66e712a8a08eef5805a46892932ad.woff │ ├── hue.0614c27197fc3ce572e161840d23b2af.png │ ├── line.567f57385ea3dde2c9aec797d07850d2.gif │ ├── loading.8732a6660b528fadfaeb35bcf568875f.gif │ ├── password-meter.64ca45e5df0f0261431766d0701ac7b3.png │ ├── roboto-v15-latin-regular.16e1d930cf13fb7a956372044b6d02d0.woff │ ├── roboto-v15-latin-regular.38861cba61c66739c1452c3a71e39852.ttf │ ├── roboto-v15-latin-regular.3d3a53586bd78d1069ae4b89a3b9aa98.svg │ ├── roboto-v15-latin-regular.7e367be02cd17a96d513ab74846bafb3.woff2 │ ├── roboto-v15-latin-regular.9f916e330c478bbfa2a0dd6614042046.eot │ └── slider_handles.1868e2550c9853a938a6211d196f9dcd.png └── bundle.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── logo.svg ├── registerServiceWorker.js ├── services │ └── github-api.js └── views │ ├── FollowerItem.js │ ├── Followers.js │ ├── Following.js │ ├── FollowingItem.js │ ├── Github.js │ ├── Repositories.js │ ├── RepositoryFilter.js │ ├── RepositoryItem.js │ ├── Search.js │ ├── StarItem.js │ ├── Stars.js │ └── UserProfile.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "babel-eslint", 4 | "extends": "airbnb", 5 | "env": { 6 | "browser": true 7 | }, 8 | "rules": { 9 | "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true }], 10 | "no-plusplus": "off", 11 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["./webpack.config.js"]}], 12 | "react/forbid-prop-types": "off", 13 | "react/jsx-filename-extension": "off", 14 | "react/no-array-index-key": "off" 15 | }, 16 | "parserOptions": { 17 | "ecmaFeatures": { 18 | "experimentalObjectRestSpread": true 19 | } 20 | }, 21 | "plugins": [ 22 | "react" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Source maps 5 | *.js.map 6 | 7 | # Files marked with gitigx 8 | *gitigx* 9 | 10 | # idea 11 | .idea 12 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/.nojekyll -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Github viewer 6 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Sudheer Jonna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## React Github Viewer using React, Axios and PrimeReact 2 | 3 | ### How to build and run 4 | ``npm install`` 5 | ``npm start`` 6 | 7 | ## Demo 8 | http://sudheerj.github.io/react-github 9 | -------------------------------------------------------------------------------- /build/assets/color.c7a33805ffda0d32bd2a9904c8b02750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/color.c7a33805ffda0d32bd2a9904c8b02750.png -------------------------------------------------------------------------------- /build/assets/fontawesome-webfont.674f50d287a8c48dc19ba404d20fe713.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/fontawesome-webfont.674f50d287a8c48dc19ba404d20fe713.eot -------------------------------------------------------------------------------- /build/assets/fontawesome-webfont.af7ae505a9eed503f8b8e6982036873e.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/fontawesome-webfont.af7ae505a9eed503f8b8e6982036873e.woff2 -------------------------------------------------------------------------------- /build/assets/fontawesome-webfont.b06871f281fee6b241d60582ae9369b9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/fontawesome-webfont.b06871f281fee6b241d60582ae9369b9.ttf -------------------------------------------------------------------------------- /build/assets/fontawesome-webfont.fee66e712a8a08eef5805a46892932ad.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/fontawesome-webfont.fee66e712a8a08eef5805a46892932ad.woff -------------------------------------------------------------------------------- /build/assets/hue.0614c27197fc3ce572e161840d23b2af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/hue.0614c27197fc3ce572e161840d23b2af.png -------------------------------------------------------------------------------- /build/assets/line.567f57385ea3dde2c9aec797d07850d2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/line.567f57385ea3dde2c9aec797d07850d2.gif -------------------------------------------------------------------------------- /build/assets/loading.8732a6660b528fadfaeb35bcf568875f.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/loading.8732a6660b528fadfaeb35bcf568875f.gif -------------------------------------------------------------------------------- /build/assets/password-meter.64ca45e5df0f0261431766d0701ac7b3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/password-meter.64ca45e5df0f0261431766d0701ac7b3.png -------------------------------------------------------------------------------- /build/assets/roboto-v15-latin-regular.16e1d930cf13fb7a956372044b6d02d0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/roboto-v15-latin-regular.16e1d930cf13fb7a956372044b6d02d0.woff -------------------------------------------------------------------------------- /build/assets/roboto-v15-latin-regular.38861cba61c66739c1452c3a71e39852.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/roboto-v15-latin-regular.38861cba61c66739c1452c3a71e39852.ttf -------------------------------------------------------------------------------- /build/assets/roboto-v15-latin-regular.3d3a53586bd78d1069ae4b89a3b9aa98.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 40 | 42 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 102 | 103 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 120 | 122 | 123 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 140 | 142 | 144 | 145 | 146 | 149 | 150 | 153 | 155 | 156 | 157 | 158 | 161 | 162 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 180 | 183 | 185 | 186 | 187 | 188 | 190 | 192 | 194 | 195 | 197 | 198 | 199 | 200 | 202 | 203 | 204 | 205 | 206 | 207 | 209 | 211 | 213 | 215 | 218 | 221 | 222 | 224 | 225 | 226 | 228 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 243 | 246 | 249 | 252 | 254 | 256 | 258 | 260 | 262 | 263 | 264 | 265 | 266 | 268 | 270 | 272 | 274 | 276 | 279 | 281 | 283 | 285 | 286 | 287 | 288 | 290 | 291 | 293 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /build/assets/roboto-v15-latin-regular.7e367be02cd17a96d513ab74846bafb3.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/roboto-v15-latin-regular.7e367be02cd17a96d513ab74846bafb3.woff2 -------------------------------------------------------------------------------- /build/assets/roboto-v15-latin-regular.9f916e330c478bbfa2a0dd6614042046.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/roboto-v15-latin-regular.9f916e330c478bbfa2a0dd6614042046.eot -------------------------------------------------------------------------------- /build/assets/slider_handles.1868e2550c9853a938a6211d196f9dcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudheerj/react-github/32c98b1cea6a155cf6262c57ca86b512fb4f8903/build/assets/slider_handles.1868e2550c9853a938a6211d196f9dcd.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ReactJS Github viewer 7 | 8 | 9 | 10 | 11 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-github", 3 | "version": "0.0.1", 4 | "private": true, 5 | "homepage": "https://sudheerj.github.io/react-github", 6 | "description": "Reactjs githubviewer", 7 | "scripts": { 8 | "start": "webpack-dev-server --devtool eval-source-map --history-api-fallback --open", 9 | "build": "webpack -p" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/sudheerj/react-github.git" 14 | }, 15 | "author": "Sudheer Jonna ", 16 | "license": "MIT", 17 | "dependencies": { 18 | "axios": "0.13.1", 19 | "bootstrap": "^3.3.5", 20 | "classnames": "^2.2.5", 21 | "font-awesome": "^4.7.0", 22 | "gh-pages": "^1.1.0", 23 | "history": "^3.0.0", 24 | "moment": "^2.10.6", 25 | "primereact": "^1.4.0", 26 | "prop-types": "^15.6.0", 27 | "react": "^16.2.0", 28 | "react-dom": "^16.2.0", 29 | "react-router": "^4.2.0", 30 | "react-router-dom": "^4.2.2", 31 | "react-scripts": "1.1.1" 32 | }, 33 | "devDependencies": { 34 | "babel-core": "^6.26.0", 35 | "babel-eslint": "^7.2.3", 36 | "babel-loader": "^7.1.2", 37 | "babel-preset-env": "^1.6.0", 38 | "babel-preset-react": "^6.24.1", 39 | "babel-preset-stage-1": "^6.24.1", 40 | "eslint": "^3.19.0", 41 | "eslint-config-airbnb": "^14.1.0", 42 | "eslint-plugin-import": "^2.2.0", 43 | "eslint-plugin-jsx-a11y": "^4.0.0", 44 | "eslint-plugin-react": "^6.10.3", 45 | "webpack": "^3.6.0", 46 | "webpack-dev-server": "^2.8.2" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-title { 18 | font-size: 1.5em; 19 | } 20 | 21 | .App-intro { 22 | font-size: large; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { transform: rotate(0deg); } 27 | to { transform: rotate(360deg); } 28 | } 29 | 30 | a { 31 | color: #0366d6; 32 | text-decoration: none; 33 | } 34 | 35 | .content-wrapper { 36 | padding: 3em 7em 1em 7em; 37 | } 38 | 39 | h1 { 40 | font-size: 32px; 41 | font-weight: 600; 42 | } 43 | 44 | .vcard-names { 45 | line-height: 1; 46 | } 47 | .vcard-fullname { 48 | font-size: 26px; 49 | line-height: 30px; 50 | } 51 | .vcard-username { 52 | font-size: 20px; 53 | font-style: normal; 54 | font-weight: 300; 55 | line-height: 24px; 56 | color: #666; 57 | } 58 | 59 | .d-block { 60 | display: block !important; 61 | } 62 | 63 | .overflow-hidden { 64 | overflow: hidden !important; 65 | } 66 | 67 | .py-3 { 68 | padding-top: 16px !important; 69 | padding-bottom: 16px !important; 70 | } 71 | 72 | .border-top { 73 | border-top: 1px #e1e4e8 solid !important; 74 | } 75 | 76 | .avatar { 77 | display: inline-block; 78 | overflow: hidden; 79 | line-height: 1; 80 | vertical-align: middle; 81 | border-radius: 3px; 82 | } 83 | 84 | .h4 { 85 | font-size: 16px !important; 86 | } 87 | 88 | .mb-1 { 89 | margin-bottom: 4px !important; 90 | } 91 | 92 | .user-profile-bio { 93 | margin-bottom: 12px; 94 | overflow: hidden; 95 | font-size: 14px; 96 | color: #6a737d; 97 | } 98 | 99 | body { 100 | min-width: 1020px; 101 | word-wrap: break-word; 102 | } 103 | 104 | .width-full { 105 | width: 100% !important; 106 | } 107 | @media (min-width: 768px) 108 | .form-control, .form-select { 109 | font-size: 14px; 110 | } 111 | .form-control, .form-select { 112 | min-height: 34px; 113 | padding: 6px 8px; 114 | font-size: 16px; 115 | line-height: 20px; 116 | color: #24292e; 117 | vertical-align: middle; 118 | background-color: #fff; 119 | background-repeat: no-repeat; 120 | background-position: right 8px center; 121 | border: 1px solid #d1d5da; 122 | border-radius: 3px; 123 | outline: none; 124 | box-shadow: inset 0 1px 2px rgba(27,31,35,0.075); 125 | width: 400px; 126 | } 127 | 128 | .list-unstyled { 129 | padding-left: 0; 130 | list-style: none; 131 | } 132 | 133 | .link-gray-dark { 134 | color: #24292e !important; 135 | } 136 | 137 | .f4 { 138 | font-size: 16px !important; 139 | } 140 | 141 | .text-gray { 142 | color: #586069 !important; 143 | } 144 | 145 | .f6 { 146 | font-size: 12px !important; 147 | } 148 | 149 | .py-1 { 150 | padding-top: 4px !important; 151 | padding-bottom: 4px !important; 152 | } 153 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | class App extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 | logo 11 |

Welcome to React

12 |
13 |

14 | To get started, edit src/App.js and save to reload. 15 |

16 |
17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | form.home { 8 | padding-top: 300px; 9 | padding-left: 500px; 10 | 11 | } 12 | 13 | .user-profile-bio { 14 | margin-bottom: 12px; 15 | overflow: hidden; 16 | font-size: 14px; 17 | color: #6a737d; 18 | } 19 | 20 | body { 21 | color: #666; 22 | } 23 | time, strong, small { 24 | color: #888; 25 | } 26 | strong { 27 | margin-left: 10px; 28 | } 29 | h2, h4 { 30 | color: #333; 31 | } 32 | h5 { 33 | font-weight: 300; 34 | font-size: 20px; 35 | } 36 | li { 37 | padding: 25px 0; 38 | } 39 | section { 40 | padding: 20px 0; 41 | } 42 | 43 | .border-bottom { 44 | border-bottom: 1px solid #eee; 45 | } 46 | 47 | section.home { 48 | padding-top: 200px; 49 | text-align: center; 50 | } 51 | section.home button { 52 | border-top-left-radius: 0; 53 | border-bottom-left-radius: 0; 54 | } 55 | 56 | section.stats { 57 | text-align: center; 58 | } 59 | section.stats span { 60 | display: inline-block; 61 | width: 80px; 62 | } 63 | section.stats h2 { 64 | margin: 0; 65 | } 66 | 67 | section.orgs img { 68 | border-radius: 3px; 69 | margin: 5px; 70 | width: 42px; 71 | height: 42px; 72 | } 73 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'primereact/resources/primereact.min.css'; 2 | import 'primereact/resources/themes/omega/theme.css'; 3 | import 'font-awesome/css/font-awesome.css'; 4 | 5 | import React from 'react' ; 6 | import ReactDOM from 'react-dom' ; 7 | import { BrowserRouter as Router, Route } from 'react-router-dom'; 8 | import './index.css'; 9 | import Search from './views/Search'; 10 | import Github from './views/Github'; 11 | 12 | 13 | 14 | ReactDOM.render( 15 | 16 |
17 | 18 | 19 | 20 |
21 |
, document.getElementById('root') 22 | ); 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/services/github-api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const BASE_URL = 'https://api.github.com'; 4 | 5 | export {getRepositories, getUserData, getStars, getFollowers, getFollowing}; 6 | 7 | function getRepositories(username) { 8 | const url = `${BASE_URL}/users/${username}/repos?per_page=250`; 9 | return axios.get(url).then(response => response.data); 10 | } 11 | 12 | function getStars(username) { 13 | const url = `${BASE_URL}/users/${username}/starred?per_page=250`; 14 | return axios.get(url).then(response => response.data); 15 | } 16 | 17 | function getFollowers(username) { 18 | const url = `${BASE_URL}/users/${username}/followers?per_page=250`; 19 | return axios.get(url).then(response => response.data); 20 | } 21 | 22 | function getFollowing(username) { 23 | const url = `${BASE_URL}/users/${username}/following?per_page=250`; 24 | return axios.get(url).then(response => response.data); 25 | } 26 | 27 | function getUserData(username) { 28 | return axios.all([ 29 | axios.get(`${BASE_URL}/users/${username}`), 30 | axios.get(`${BASE_URL}/users/${username}/orgs`), 31 | ]) 32 | .then(([user, orgs]) => ({ 33 | user: user.data, 34 | orgs: orgs.data, 35 | })); 36 | } 37 | -------------------------------------------------------------------------------- /src/views/FollowerItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default FollowerItem; 5 | 6 | function FollowerItem({follower}) { 7 | return ( 8 |
9 |
10 |
  • 11 | 12 | User Avatar 17 | 18 |
  • 19 | 20 |
    21 |
    22 | {follower.name} 23 | {follower.login} 24 |
    25 |
    26 | ); 27 | } 28 | 29 | FollowerItem.propTypes = { 30 | follower: PropTypes.shape({ 31 | pushed_at: PropTypes.string, 32 | language: PropTypes.string, 33 | stargazers_count: PropTypes.number, 34 | forks_count: PropTypes.number, 35 | html_url: PropTypes.string, 36 | name: PropTypes.string, 37 | description: PropTypes.string, 38 | }), 39 | }; 40 | FollowerItem.defaultProps = { 41 | follower: {}, 42 | }; 43 | -------------------------------------------------------------------------------- /src/views/Followers.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FollowerItem from './FollowerItem'; 3 | import {getFollowers} from '../services/github-api' 4 | import PropTypes from 'prop-types' 5 | 6 | export default class Followers extends React.Component { 7 | constructor() { 8 | super() 9 | this.state = {followers: []} 10 | } 11 | 12 | getFollowers() { 13 | const {username} = this.props 14 | this.props.getFollowers(username).then(followers => { 15 | this.setState({followers}); 16 | }); 17 | } 18 | 19 | componentWillMount() { 20 | this.getFollowers(); 21 | } 22 | 23 | render() { 24 | const {followers} = this.state 25 | return ( 26 | 29 | ); 30 | } 31 | } 32 | 33 | Followers.propTypes = { 34 | username: PropTypes.string.isRequired, 35 | getFollowers: PropTypes.func, 36 | }; 37 | Followers.defaultProps = {getFollowers} 38 | 39 | function renderFollowers(followers) { 40 | console.log(followers); 41 | return followers 42 | .map(follower => ); 43 | } 44 | -------------------------------------------------------------------------------- /src/views/Following.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FollowingItem from './FollowingItem'; 3 | import {getFollowing} from '../services/github-api' 4 | import PropTypes from 'prop-types' 5 | 6 | export default class Following extends React.Component { 7 | constructor() { 8 | super() 9 | this.state = {following: []} 10 | } 11 | 12 | getFollowing() { 13 | const {username} = this.props 14 | this.props.getFollowing(username).then(following => { 15 | this.setState({following}); 16 | }); 17 | } 18 | 19 | componentWillMount() { 20 | this.getFollowing(); 21 | } 22 | 23 | render() { 24 | const {following} = this.state 25 | return ( 26 |
      27 | {renderFollowing(following)} 28 |
    29 | ); 30 | } 31 | } 32 | 33 | Following.propTypes = { 34 | username: PropTypes.string.isRequired, 35 | getFollowing: PropTypes.func, 36 | }; 37 | Following.defaultProps = {getFollowing} 38 | 39 | function renderFollowing(following) { 40 | return following 41 | .map(followingUser => ); 42 | } 43 | -------------------------------------------------------------------------------- /src/views/FollowingItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default FollowingItem; 5 | 6 | function FollowingItem({following}) { 7 | return ( 8 |
    9 |
    10 |
  • 11 | 12 | User Avatar 17 | 18 |
  • 19 | 20 |
    21 |
    22 | {following.name} 23 | {following.login} 24 |
    25 |
    26 | ); 27 | } 28 | 29 | FollowingItem.propTypes = { 30 | following: PropTypes.shape({ 31 | stargazers_count: PropTypes.number, 32 | forks_count: PropTypes.number, 33 | html_url: PropTypes.string, 34 | name: PropTypes.string, 35 | description: PropTypes.string, 36 | }), 37 | }; 38 | 39 | FollowingItem.defaultProps = { 40 | following: {}, 41 | }; 42 | -------------------------------------------------------------------------------- /src/views/Github.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UserProfile from './UserProfile' 3 | import Repositories from './Repositories' 4 | import Stars from './Stars' 5 | import Followers from './Followers' 6 | import Following from './Following' 7 | import RepositoryFilter from './RepositoryFilter' 8 | import PropTypes from 'prop-types'; 9 | import {TabView, TabPanel} from 'primereact/components/tabview/TabView'; 10 | 11 | export default class Github extends React.Component { 12 | constructor(props) { 13 | super(props) 14 | this.state = {repoFilter: '', starFilter: ''} 15 | 16 | } 17 | 18 | handleRepoFilterUpdate = (repoFilter) => { 19 | this.setState({repoFilter}) 20 | } 21 | 22 | handleStarFilterUpdate = (starFilter) => { 23 | this.setState({starFilter}) 24 | } 25 | 26 | render() { 27 | const {username} = this.props.match.params 28 | const {repoFilter, starFilter} = this.state 29 | return ( 30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
    54 |
    55 |
    56 |
    57 | ); 58 | } 59 | } 60 | 61 | Github.propTypes = { 62 | params: PropTypes.shape({ 63 | username: PropTypes.string, 64 | }), 65 | } 66 | -------------------------------------------------------------------------------- /src/views/Repositories.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import RepositoryItem from './RepositoryItem'; 3 | import {getRepositories} from '../services/github-api' 4 | import PropTypes from 'prop-types' 5 | 6 | export default class Repositories extends React.Component { 7 | constructor() { 8 | super() 9 | this.state = {repositories: []} 10 | } 11 | 12 | getRepositories() { 13 | const {username} = this.props 14 | this.props.getRepositories(username).then(repositories => { 15 | this.setState({repositories}); 16 | }); 17 | } 18 | 19 | componentWillMount() { 20 | this.getRepositories(); 21 | } 22 | 23 | render() { 24 | const {repositories} = this.state 25 | const {filter} = this.props 26 | return ( 27 |
      28 | {renderRepositories(repositories, filter.toLowerCase())} 29 |
    30 | ); 31 | } 32 | } 33 | 34 | Repositories.propTypes = { 35 | filter: PropTypes.string.isRequired, 36 | username: PropTypes.string.isRequired, 37 | getRepositories: PropTypes.func, 38 | }; 39 | Repositories.defaultProps = {getRepositories} 40 | 41 | function renderRepositories(repositories, filter) { 42 | return repositories 43 | .filter(repository => { 44 | return !filter || 45 | (repository.name && repository.name.toLowerCase().includes(filter)) || 46 | (repository.description && repository.description.toLowerCase().includes(filter)); 47 | }) 48 | .sort((a, b) => Date.parse(b.pushed_at) - Date.parse(a.pushed_at)) 49 | .map(repository => ); 50 | } 51 | -------------------------------------------------------------------------------- /src/views/RepositoryFilter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types' 3 | import {InputText} from 'primereact/components/inputtext/InputText'; 4 | 5 | 6 | export default class RepositoryFilter extends React.Component { 7 | render() { 8 | return ( 9 |
    10 | this.props.onUpdate(value)} 14 | /> 15 |
    16 | ); 17 | } 18 | } 19 | 20 | RepositoryFilter.propTypes = { 21 | onUpdate: PropTypes.func.isRequired, 22 | }; 23 | -------------------------------------------------------------------------------- /src/views/RepositoryItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export default RepositoryItem; 6 | 7 | function RepositoryItem({repository}) { 8 | const timeUpdated = moment(repository.pushed_at).fromNow(); 9 | return ( 10 |
  • 11 |

    {repository.name}

    12 |

    {repository.description}

    13 |
    14 | {repository.language} 15 | {repository.stargazers_count} 16 | {repository.forks_count} 17 | 18 |
    19 |
  • 20 | ); 21 | } 22 | 23 | RepositoryItem.propTypes = { 24 | repository: PropTypes.shape({ 25 | pushed_at: PropTypes.string, 26 | language: PropTypes.string, 27 | stargazers_count: PropTypes.number, 28 | forks_count: PropTypes.number, 29 | html_url: PropTypes.string, 30 | name: PropTypes.string, 31 | description: PropTypes.string, 32 | }), 33 | }; 34 | 35 | RepositoryItem.defaultProps = { 36 | repository: {}, 37 | }; 38 | -------------------------------------------------------------------------------- /src/views/Search.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Card} from "primereact/components/card/Card"; 3 | import {Button} from "primereact/components/button/Button"; 4 | import {InputText} from "primereact/components/inputtext/InputText"; 5 | import {withRouter} from "react-router-dom"; 6 | import PropTypes from 'prop-types'; 7 | 8 | class Search extends React.Component { 9 | constructor() { 10 | super(); 11 | this.state = { 12 | value: '' 13 | }; 14 | this.handleClick = this.handleClick.bind(this); 15 | } 16 | 17 | handleClick = (e) => { 18 | e.preventDefault(); 19 | this.props.history.push(`/react-github/${this.state.value}`); 20 | } 21 | 22 | render() { 23 | 24 | return ( 25 | 26 |
    27 | 29 |
    30 | 31 | this.setState({value: e.target.value})}/> 32 |
    34 |
    35 |
    36 | ); 37 | } 38 | } 39 | 40 | export default withRouter(Search); 41 | 42 | Search.propTypes = { 43 | params: PropTypes.shape({ 44 | username: PropTypes.string, 45 | }), 46 | } 47 | -------------------------------------------------------------------------------- /src/views/StarItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import moment from 'moment'; 3 | import PropTypes from 'prop-types'; 4 | 5 | export default StarItem; 6 | 7 | function StarItem({repo}) { 8 | const timeUpdated = moment(repo.pushed_at).fromNow(); 9 | return ( 10 |
  • 11 |

    {repo.name}

    12 |

    {repo.description}

    13 |
    14 | {repo.language} 15 | {repo.stargazers_count} 16 | {repo.forks_count} 17 | 18 |
    19 |
  • 20 | ); 21 | } 22 | 23 | StarItem.propTypes = { 24 | repo: PropTypes.shape({ 25 | pushed_at: PropTypes.string, 26 | language: PropTypes.string, 27 | stargazers_count: PropTypes.number, 28 | forks_count: PropTypes.number, 29 | html_url: PropTypes.string, 30 | name: PropTypes.string, 31 | description: PropTypes.string, 32 | }), 33 | }; 34 | 35 | StarItem.defaultProps = { 36 | repo: {}, 37 | }; 38 | -------------------------------------------------------------------------------- /src/views/Stars.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import StarItem from './StarItem'; 3 | import {getStars} from '../services/github-api' 4 | import PropTypes from 'prop-types' 5 | 6 | export default class Stars extends React.Component { 7 | constructor() { 8 | super() 9 | this.state = {stars: []} 10 | } 11 | 12 | getStars() { 13 | const {username} = this.props 14 | this.props.getStars(username).then(stars => { 15 | this.setState({stars}); 16 | }); 17 | } 18 | 19 | componentWillMount() { 20 | this.getStars(); 21 | } 22 | 23 | render() { 24 | const {stars} = this.state 25 | const {filter} = this.props 26 | return ( 27 |
      28 | {renderStars(stars, filter.toLowerCase())} 29 |
    30 | ); 31 | } 32 | } 33 | 34 | Stars.propTypes = { 35 | filter: PropTypes.string.isRequired, 36 | username: PropTypes.string.isRequired, 37 | getStars: PropTypes.func, 38 | }; 39 | Stars.defaultProps = {getStars} 40 | 41 | function renderStars(stars, filter) { 42 | return stars 43 | .filter(star => { 44 | return !filter || 45 | (star.name && star.name.toLowerCase().includes(filter)) || 46 | (star.description && star.description.toLowerCase().includes(filter)); 47 | }) 48 | .sort((a, b) => Date.parse(b.pushed_at) - Date.parse(a.pushed_at)) 49 | .map(repo => ); 50 | } 51 | -------------------------------------------------------------------------------- /src/views/UserProfile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {getUserData} from '../services/github-api' 3 | import PropTypes from 'prop-types'; 4 | import '../App.css'; 5 | 6 | export default class UserProfile extends React.Component { 7 | constructor() { 8 | super() 9 | this.state = {user: {}, orgs: []} 10 | } 11 | 12 | getUser() { 13 | const {username} = this.props 14 | getUserData(username) 15 | .then(({user, orgs}) => { 16 | this.setState({user, orgs}); 17 | }); 18 | } 19 | 20 | componentWillMount() { 21 | this.getUser(); 22 | } 23 | 24 | render() { 25 | const {user, orgs} = this.state; 26 | return ( 27 |
    28 |
    29 | User Avatar 37 |

    38 | {user.name} 39 | {user.login} 40 |

    41 |
    42 |
    {user.bio}
    43 |
    44 |
    45 |
    46 |

    Organizations

    47 | {orgs.map(org => ( 48 | Organization Avatar 54 | ))} 55 | {/**/} 56 |
    57 |
    58 | 59 | Repositories 60 |

    {user.public_repos}

    61 | Followers 62 |

    {user.followers}

    63 | Following 64 |

    {user.following}

    65 |
    66 | 67 | 68 | 69 | 70 | 71 | 72 |
    73 |
    74 | ); 75 | } 76 | } 77 | 78 | UserProfile.propTypes = { 79 | username: PropTypes.string.isRequired 80 | } 81 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: `${__dirname}/src/index.js`, 5 | output: { 6 | path: `${__dirname}/build`, 7 | publicPath: '/react-github/build/', 8 | filename: 'bundle.js', 9 | }, 10 | 11 | module: { 12 | rules: [ 13 | { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }, 14 | { test: /\.css$/, loader:['style-loader', 'css-loader']}, 15 | { 16 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/, 17 | loader: 'file-loader?name=assets/[name].[hash].[ext]' 18 | } 19 | ], 20 | }, 21 | 22 | plugins: process.argv.indexOf('-p') === -1 ? [] : [ 23 | new webpack.optimize.UglifyJsPlugin({ 24 | output: { 25 | comments: false, 26 | }, 27 | }), 28 | ], 29 | }; 30 | --------------------------------------------------------------------------------