├── .gitignore ├── README.md ├── package.json ├── public ├── _redirects ├── favicon.ico ├── index.html └── robots.txt ├── src ├── components │ ├── App.js │ └── TeamLogo.js ├── hooks │ ├── useArticle.js │ ├── useFetch.js │ ├── usePlayers.js │ ├── useTeam.js │ ├── useTeamNames.js │ └── useTeamsArticles.js ├── index.css └── 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | UI.dev Logo 6 | 7 |
8 |

9 | 10 |

React Router v5 Course Project - Hash History Basketball League

11 | 12 | ### Info 13 | 14 | This is the repository for [UI.dev](https://ui.dev)'s "React Router v5" course project. 15 | 16 | For more information on the course, visit __[ui.dev/react-router-v5](https://ui.dev/react-router-v5/)__. 17 | 18 | ### Project 19 | 20 | This project is an app for a fictional Basketball league. 21 | 22 | You can view the final project __[here](https://basketball-v5.ui.dev/)__ 23 | 24 | ### Branches 25 | 26 | Every `(Project)` video in the course coincides with a branch in this repo. If you want to compare your code with Tyler's or you just want to play around with the code, check out the different branches. 27 | 28 | Below every `(Project)` video in the course will be a direct link to both the commit for that video as well as its branch. 29 | 30 | 31 | 32 | ### Project Preview 33 | 34 | ![](https://user-images.githubusercontent.com/2933430/82761753-f994a380-9db9-11ea-98af-922c37266d46.png) 35 | 36 | ![](https://user-images.githubusercontent.com/2933430/82761748-f7cae000-9db9-11ea-9cce-8e6ab5dcc2c7.png) 37 | 38 | ![](https://user-images.githubusercontent.com/2933430/82761747-f699b300-9db9-11ea-9e59-a79c65fd6536.png) 39 | 40 | ![](https://user-images.githubusercontent.com/2933430/82761744-f39ec280-9db9-11ea-954d-33feb6bfc706.png) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hash-history-basketball-league-v5", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "react": "^16.13.1", 6 | "react-dom": "^16.13.1", 7 | "react-scripts": "3.4.1" 8 | }, 9 | "scripts": { 10 | "start": "react-scripts start", 11 | "build": "react-scripts build", 12 | "eject": "react-scripts eject" 13 | }, 14 | "eslintConfig": { 15 | "extends": "react-app" 16 | }, 17 | "browserslist": { 18 | "production": [ 19 | ">0.2%", 20 | "not dead", 21 | "not op_mini all" 22 | ], 23 | "development": [ 24 | "last 1 chrome version", 25 | "last 1 firefox version", 26 | "last 1 safari version" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uidotdev/react-router-v5-course/e67171e23c9c7b423ba9b484f3500aacbf08357c/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 26 | Hash History Basketball League 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export default function App () { 4 | return ( 5 |
6 | Hash History Basketball League 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /src/components/TeamLogo.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | const logos = { 4 | bulls: , 5 | foxes: , 6 | hedgehogs: , 7 | koalas: , 8 | lemurs: , 9 | } 10 | 11 | export default function TeamLogo ({ width='200px ', id, ...rest}) { 12 | return ( 13 | 14 | {logos[id]} 15 | 16 | ) 17 | } -------------------------------------------------------------------------------- /src/hooks/useArticle.js: -------------------------------------------------------------------------------- 1 | import useFetch from './useFetch' 2 | 3 | export default function useArticle (args) { 4 | return useFetch('/article', 'POST', JSON.stringify(args)) 5 | } -------------------------------------------------------------------------------- /src/hooks/useFetch.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | const API = 'https://api.ui.dev/hash-history-basketball-league' 4 | 5 | export default function useFetch (path, method, body='') { 6 | const [response, setResponse] = React.useState(null) 7 | const [loading, setLoading] = React.useState(true) 8 | 9 | React.useEffect(() => { 10 | const abortController = new AbortController() 11 | const signal = abortController.signal 12 | 13 | fetch(`${API}${path}`, { 14 | method, 15 | ...(body ? { body } : {}), 16 | headers: { 17 | 'Accept': 'application/json', 18 | 'Content-Type': 'application/json', 19 | }, 20 | }) 21 | .then((res) => res.json()) 22 | .then(({ body }) => body ? JSON.parse(body) : null) 23 | .then((data) => { 24 | if (!signal.aborted) { 25 | setResponse(data) 26 | setLoading(false) 27 | } 28 | }) 29 | .catch((error) => console.warn('Uh-oh.', error)) 30 | 31 | return () => abortController.abort() 32 | }, [path, method, body]) 33 | 34 | return { response, loading } 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/usePlayers.js: -------------------------------------------------------------------------------- 1 | import useFetch from './useFetch' 2 | 3 | export default function usePlayers (team) { 4 | return useFetch('/players', 'POST', JSON.stringify({ team })) 5 | } -------------------------------------------------------------------------------- /src/hooks/useTeam.js: -------------------------------------------------------------------------------- 1 | import useFetch from './useFetch' 2 | 3 | export default function useTeam (team) { 4 | return useFetch('/team', 'POST', JSON.stringify({ team })) 5 | } -------------------------------------------------------------------------------- /src/hooks/useTeamNames.js: -------------------------------------------------------------------------------- 1 | import useFetch from './useFetch' 2 | 3 | export default function useTeamNames () { 4 | return useFetch('/teams', 'GET') 5 | } -------------------------------------------------------------------------------- /src/hooks/useTeamsArticles.js: -------------------------------------------------------------------------------- 1 | import useFetch from './useFetch' 2 | 3 | export default function useTeamsArticles (team) { 4 | return useFetch('/articles', 'POST', JSON.stringify({ team })) 5 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://ui.dev/font"); 2 | 3 | :root { 4 | --black: #000; 5 | --white: #fff; 6 | --red: #f32827; 7 | --purple: #a42ce9; 8 | --blue: #2d7fea; 9 | --yellow: #f4f73e; 10 | --pink: #eb30c1; 11 | --gold: #ffd500; 12 | --aqua: #2febd2; 13 | --gray: #282c35; 14 | } 15 | 16 | *, 17 | *:before, 18 | *:after { 19 | box-sizing: inherit; 20 | } 21 | 22 | html { 23 | font-family: proxima-nova, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif; 24 | text-rendering: optimizeLegibility; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | box-sizing: border-box; 28 | font-size: 18px; 29 | } 30 | 31 | body { 32 | margin: 0; 33 | padding: 0; 34 | min-height: 100vh; 35 | background: var(--black); 36 | color: var(--white); 37 | } 38 | 39 | a { 40 | color: var(--gold); 41 | text-decoration: none; 42 | } 43 | 44 | li { 45 | list-style-type: none; 46 | } 47 | 48 | .border-container { 49 | padding: 0; 50 | margin: 0; 51 | display: flex; 52 | margin-bottom: 20px; 53 | } 54 | 55 | .border-item { 56 | width: 20vw; 57 | height: 12px; 58 | } 59 | 60 | .container { 61 | position: relative; 62 | max-width: 1000px; 63 | min-width: 280px; 64 | margin: 0 auto; 65 | padding: 20px; 66 | } 67 | 68 | .large-header { 69 | font-size: 105px; 70 | text-align: center; 71 | font-weight: 900; 72 | } 73 | 74 | .medium-header { 75 | font-size: 55px; 76 | text-align: center; 77 | margin: 20px 0; 78 | font-weight: 900; 79 | } 80 | 81 | .header { 82 | margin: 20px 0; 83 | font-weight: 300; 84 | font-size: 35px; 85 | font-weight: 900; 86 | } 87 | 88 | .center { 89 | display: block; 90 | margin: 0 auto; 91 | } 92 | 93 | .text-center { 94 | text-align: center; 95 | } 96 | 97 | .row { 98 | display: flex; 99 | justify-content: space-around; 100 | } 101 | 102 | .home-grid { 103 | display: flex; 104 | flex-direction: row; 105 | justify-content: center; 106 | } 107 | 108 | .navbar { 109 | font-size: 16px; 110 | text-transform: uppercase; 111 | flex: 1; 112 | display: flex; 113 | justify-content: space-between; 114 | font-weight: bold; 115 | } 116 | 117 | .nav-links a { 118 | padding: 0 10px; 119 | } 120 | 121 | .two-column { 122 | display: flex; 123 | } 124 | 125 | .panel { 126 | flex: 1; 127 | display: flex; 128 | flex-direction: column; 129 | align-items: center; 130 | justify-content: center; 131 | max-width: 1200px; 132 | margin: 0 auto; 133 | } 134 | 135 | .avatar { 136 | width: 200px; 137 | border-radius: 100px; 138 | } 139 | 140 | .info-list { 141 | margin-bottom: 30px; 142 | padding: 0; 143 | } 144 | 145 | .info-list li { 146 | font-size: 33px; 147 | margin: 20px 0; 148 | font-weight: 700; 149 | } 150 | 151 | .info-list div { 152 | font-size: 24px; 153 | font-weight: 300; 154 | } 155 | 156 | .sidebar-list { 157 | display: flex; 158 | padding: 10px; 159 | flex-direction: column; 160 | padding: 0; 161 | } 162 | 163 | .sidebar-list a { 164 | line-height: 25px; 165 | } 166 | 167 | .sidebar-instruction { 168 | display: flex; 169 | justify-content: center; 170 | align-items: center; 171 | flex: 1; 172 | font-weight: 900; 173 | font-size: 22px; 174 | } 175 | 176 | .articles { 177 | margin: 0; 178 | padding: 0; 179 | } 180 | 181 | .articles li { 182 | font-size: 28px; 183 | margin-bottom: 30px; 184 | cursor: pointer; 185 | } 186 | 187 | .article { 188 | margin: 20px; 189 | padding: 20px; 190 | } 191 | 192 | .article p { 193 | line-height: 35px; 194 | font-size: 20px; 195 | } 196 | 197 | .article-title { 198 | margin: 0; 199 | font-weight: 300; 200 | } 201 | 202 | .article-date { 203 | font-size: 14px; 204 | padding: 0; 205 | } 206 | 207 | .btn-main { 208 | max-width: 240px; 209 | min-width: 150px; 210 | padding: 10px; 211 | border: 2px solid black; 212 | color: var(--white); 213 | text-align: center; 214 | background: var(--pink); 215 | font-weight: 900; 216 | font-size: 15px; 217 | } 218 | 219 | .btn-main:hover { 220 | cursor: pointer; 221 | font-weight: 500; 222 | } 223 | 224 | .championships { 225 | display: flex; 226 | margin: 0; 227 | padding: 0; 228 | } 229 | 230 | .championships li { 231 | margin: 0 10px; 232 | } 233 | 234 | @media (max-width: 680px) { 235 | .container { 236 | padding: 8px; 237 | } 238 | 239 | .large-header { 240 | font-size: 60px; 241 | } 242 | 243 | .medium-header { 244 | font-size: 40px; 245 | } 246 | 247 | .header { 248 | font-size: 25px; 249 | margin: 10px 0; 250 | } 251 | 252 | .avatar { 253 | width: 125px; 254 | border-radius: 62.5px; 255 | } 256 | 257 | .home-grid { 258 | flex-direction: column; 259 | align-items: center; 260 | } 261 | 262 | .info-list li { 263 | font-size: 20px; 264 | } 265 | 266 | .info-list div { 267 | font-size: 16px; 268 | } 269 | .championships li { 270 | font-size: 12px; 271 | } 272 | 273 | .sidebar-list { 274 | font-size: 12px; 275 | } 276 | 277 | .article { 278 | padding: 5px; 279 | margin: 5px; 280 | } 281 | 282 | .article-title { 283 | font-size: 16px; 284 | } 285 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './components/App' 5 | 6 | function ColorfulBorder() { 7 | return ( 8 | 15 | ) 16 | } 17 | 18 | ReactDOM.render( 19 | 20 | 21 | 22 | , 23 | document.getElementById('root') 24 | ) --------------------------------------------------------------------------------