├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.js ├── Home.js ├── Movies.js ├── SearchForm.js ├── SingleMovie.js ├── context.js ├── index.css └── index.js /.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 | .env -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-projects-21-movie-db/66297777b7f5831fa8e9bcd38fc125bf0b8a6965/README.md -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reminder", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.5.0", 8 | "@testing-library/user-event": "^7.2.1", 9 | "react": "^16.13.1", 10 | "react-dom": "^16.13.1", 11 | "react-router-dom": "^5.2.0", 12 | "react-scripts": "3.4.3" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "CI= react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-projects-21-movie-db/66297777b7f5831fa8e9bcd38fc125bf0b8a6965/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Movie DB Complete 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-projects-21-movie-db/66297777b7f5831fa8e9bcd38fc125bf0b8a6965/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/john-smilga/react-projects-21-movie-db/66297777b7f5831fa8e9bcd38fc125bf0b8a6965/public/logo512.png -------------------------------------------------------------------------------- /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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route } from 'react-router-dom' 3 | 4 | import Home from './Home' 5 | import Movie from './SingleMovie' 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | }> 14 | 15 | ) 16 | } 17 | 18 | export default App 19 | -------------------------------------------------------------------------------- /src/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Form from './SearchForm' 3 | import Movies from './Movies' 4 | const Home = () => { 5 | return ( 6 |
7 |
8 | 9 |
10 | ) 11 | } 12 | 13 | export default Home 14 | -------------------------------------------------------------------------------- /src/Movies.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useGlobalContext } from './context' 3 | import { Link } from 'react-router-dom' 4 | const url = 5 | 'https://upload.wikimedia.org/wikipedia/commons/f/fc/No_picture_available.png' 6 | 7 | const Movies = () => { 8 | const { movies, isLoading } = useGlobalContext() 9 | 10 | if (isLoading) { 11 | return
12 | } 13 | 14 | return ( 15 |
16 | {movies.map((movie) => { 17 | const { imdbID: id, Poster: poster, Title: title, Year: year } = movie 18 | 19 | return ( 20 | 21 |
22 | {title} 23 |
24 |

{title}

25 |

{year}

26 |
27 |
28 | 29 | ) 30 | })} 31 |
32 | ) 33 | } 34 | 35 | export default Movies 36 | -------------------------------------------------------------------------------- /src/SearchForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useGlobalContext } from './context' 3 | const SearchForm = () => { 4 | const { query, setQuery, isError } = useGlobalContext() 5 | return ( 6 | e.preventDefault()}> 7 |

search movies

8 | setQuery(e.target.value)} 13 | /> 14 | {isError &&
movie not found !
} 15 | 16 | ) 17 | } 18 | 19 | export default SearchForm 20 | -------------------------------------------------------------------------------- /src/SingleMovie.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useParams, Link } from 'react-router-dom'; 3 | import { API_ENDPOINT_SINGLE } from './context'; 4 | const SingleMovie = () => { 5 | const { id } = useParams(); 6 | const [movie, setMovie] = useState({}); 7 | const [isLoading, setLoading] = useState(true); 8 | const fetchMovie = async (url) => { 9 | const response = await fetch(url); 10 | const data = await response.json(); 11 | setMovie(data); 12 | setLoading(false); 13 | }; 14 | 15 | useEffect(() => { 16 | fetchMovie(`${API_ENDPOINT_SINGLE}&i=${id}`); 17 | }, [id]); 18 | 19 | if (isLoading) { 20 | return
; 21 | } 22 | console.log(movie); 23 | const { Poster: poster, Title: title, Plot: plot, Year: year } = movie; 24 | return ( 25 |
26 | {title} 27 |
28 |

{title}

29 |

{plot}

30 |

{year}

31 | 32 | back to movies 33 | 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default SingleMovie; 40 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from 'react'; 2 | 3 | export const API_ENDPOINT = `https://www.omdbapi.com/?apikey=${process.env.REACT_APP_MOVIE_API_KEY}`; 4 | export const API_ENDPOINT_SINGLE = `https://www.omdbapi.com/?apikey=${process.env.REACT_APP_MOVIE_API_KEY_2}`; 5 | 6 | const AppContext = React.createContext(); 7 | 8 | const AppProvider = ({ children }) => { 9 | const [isLoading, setIsLoading] = useState(true); 10 | const [isError, setIsError] = useState(false); 11 | const [movies, setMovies] = useState([]); 12 | const [query, setQuery] = useState('batman'); 13 | const fetchMovies = async (url) => { 14 | setIsLoading(true); 15 | try { 16 | const response = await fetch(url); 17 | const data = await response.json(); 18 | 19 | if (data.Response === 'True') { 20 | setMovies(data.Search); 21 | setIsError(false); 22 | } else { 23 | setIsError(true); 24 | } 25 | setIsLoading(false); 26 | } catch (error) { 27 | console.log(error); 28 | } 29 | }; 30 | 31 | useEffect(() => { 32 | fetchMovies(`${API_ENDPOINT}&s=${query}`); 33 | }, [query]); 34 | 35 | return ( 36 | 39 | {children} 40 | 41 | ); 42 | }; 43 | // make sure use 44 | export const useGlobalContext = () => { 45 | return useContext(AppContext); 46 | }; 47 | 48 | export { AppContext, AppProvider }; 49 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | =============== 3 | Variables 4 | =============== 5 | */ 6 | 7 | :root { 8 | /* dark shades of primary color*/ 9 | --clr-primary-1: hsl(205, 86%, 17%); 10 | --clr-primary-2: hsl(205, 77%, 27%); 11 | --clr-primary-3: hsl(205, 72%, 37%); 12 | --clr-primary-4: hsl(205, 63%, 48%); 13 | /* primary/main color */ 14 | --clr-primary-5: hsl(205, 78%, 60%); 15 | /* lighter shades of primary color */ 16 | --clr-primary-6: hsl(205, 89%, 70%); 17 | --clr-primary-7: hsl(205, 90%, 76%); 18 | --clr-primary-8: hsl(205, 86%, 81%); 19 | --clr-primary-9: hsl(205, 90%, 88%); 20 | --clr-primary-10: hsl(205, 100%, 96%); 21 | /* darkest grey - used for headings */ 22 | --clr-grey-1: hsl(209, 61%, 16%); 23 | --clr-grey-2: hsl(211, 39%, 23%); 24 | --clr-grey-3: hsl(209, 34%, 30%); 25 | --clr-grey-4: hsl(209, 28%, 39%); 26 | /* grey used for paragraphs */ 27 | --clr-grey-5: hsl(210, 22%, 49%); 28 | --clr-grey-6: hsl(209, 23%, 60%); 29 | --clr-grey-7: hsl(211, 27%, 70%); 30 | --clr-grey-8: hsl(210, 31%, 80%); 31 | --clr-grey-9: hsl(212, 33%, 89%); 32 | --clr-grey-10: hsl(210, 36%, 96%); 33 | --clr-white: #fff; 34 | --clr-red-dark: hsl(360, 67%, 44%); 35 | --clr-red-light: hsl(360, 71%, 66%); 36 | --clr-green-dark: hsl(125, 67%, 44%); 37 | --clr-green-light: hsl(125, 71%, 66%); 38 | --clr-black: #222; 39 | --transition: all 0.3s linear; 40 | --spacing: 0.1rem; 41 | --radius: 0.25rem; 42 | --light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); 43 | --dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); 44 | --max-width: 1170px; 45 | --fixed-width: 620px; 46 | } 47 | /* 48 | =============== 49 | Global Styles 50 | =============== 51 | */ 52 | 53 | *, 54 | ::after, 55 | ::before { 56 | margin: 0; 57 | padding: 0; 58 | box-sizing: border-box; 59 | } 60 | body { 61 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 62 | Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 63 | background: var(--clr-grey-10); 64 | color: var(--clr-grey-1); 65 | line-height: 1.5; 66 | font-size: 0.875rem; 67 | } 68 | ul { 69 | list-style-type: none; 70 | } 71 | a { 72 | text-decoration: none; 73 | } 74 | h1, 75 | h2, 76 | h3, 77 | h4 { 78 | letter-spacing: var(--spacing); 79 | text-transform: capitalize; 80 | line-height: 1.25; 81 | margin-bottom: 0.75rem; 82 | } 83 | h1 { 84 | font-size: 2.5rem; 85 | } 86 | h2 { 87 | font-size: 2rem; 88 | } 89 | h3 { 90 | font-size: 1.25rem; 91 | } 92 | h4 { 93 | font-size: 0.875rem; 94 | } 95 | p { 96 | margin-bottom: 1.25rem; 97 | color: var(--clr-grey-3); 98 | } 99 | @media screen and (min-width: 800px) { 100 | h1 { 101 | font-size: 3rem; 102 | } 103 | h2 { 104 | font-size: 2.5rem; 105 | } 106 | h3 { 107 | font-size: 1.75rem; 108 | } 109 | h4 { 110 | font-size: 1rem; 111 | } 112 | body { 113 | font-size: 1rem; 114 | } 115 | h1, 116 | h2, 117 | h3, 118 | h4 { 119 | line-height: 1; 120 | } 121 | } 122 | /* global classes */ 123 | 124 | /* section */ 125 | .section { 126 | width: 90vw; 127 | margin: 0 auto; 128 | max-width: var(--max-width); 129 | } 130 | 131 | @media screen and (min-width: 992px) { 132 | .section { 133 | width: 95vw; 134 | } 135 | } 136 | /* 137 | =============== 138 | Search 139 | =============== 140 | */ 141 | .search-form { 142 | width: 90vw; 143 | max-width: var(--max-width); 144 | margin: 0 auto; 145 | margin-top: 5rem; 146 | margin-bottom: 3rem; 147 | } 148 | .form-input { 149 | width: 100%; 150 | border: transparent; 151 | max-width: 600px; 152 | background: var(--clr-white); 153 | padding: 1rem; 154 | font-size: 1rem; 155 | border-radius: var(--radius); 156 | color: var(--clr-grey-3); 157 | letter-spacing: var(--spacing); 158 | margin-top: 1rem; 159 | } 160 | .error { 161 | color: var(--clr-red-dark); 162 | text-transform: capitalize; 163 | padding-top: 0.5rem; 164 | letter-spacing: var(--spacing); 165 | } 166 | /* 167 | =============== 168 | Loading 169 | =============== 170 | */ 171 | 172 | @keyframes spinner { 173 | to { 174 | transform: rotate(360deg); 175 | } 176 | } 177 | 178 | .loading { 179 | width: 6rem; 180 | height: 6rem; 181 | margin: 0 auto; 182 | margin-top: 10rem; 183 | border-radius: 50%; 184 | border: 3px solid #ccc; 185 | border-top-color: var(--clr-primary-5); 186 | animation: spinner 0.6s linear infinite; 187 | } 188 | 189 | /* 190 | =============== 191 | Movies 192 | =============== 193 | */ 194 | .movies { 195 | width: 90vw; 196 | max-width: var(--max-width); 197 | display: grid; 198 | gap: 2rem; 199 | margin: 0 auto; 200 | padding-bottom: 5rem; 201 | padding-top: 3rem; 202 | } 203 | .movie { 204 | position: relative; 205 | overflow: hidden; 206 | } 207 | .movie img { 208 | width: 100%; 209 | height: 400px; 210 | display: block; 211 | object-fit: cover; 212 | } 213 | .movie-info { 214 | position: absolute; 215 | bottom: 0; 216 | left: 0; 217 | width: 100%; 218 | padding: 0.5rem 1rem; 219 | background: rgba(0, 0, 0, 0.6); 220 | transform: translateY(100%); 221 | transition: var(--transition); 222 | } 223 | .movie-info h4 { 224 | color: var(--clr-white); 225 | margin-bottom: 0.25rem; 226 | } 227 | .movie-info p { 228 | margin-bottom: 0; 229 | color: var(--clr-white); 230 | } 231 | .movie:hover .movie-info { 232 | transform: translateY(0); 233 | } 234 | @media screen and (min-width: 576px) { 235 | .movies { 236 | grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); 237 | } 238 | } 239 | 240 | /* 241 | =============== 242 | Single Movie 243 | =============== 244 | */ 245 | 246 | .single-movie { 247 | width: 90vw; 248 | max-width: var(--max-width); 249 | margin: 4rem auto; 250 | display: grid; 251 | gap: 2rem; 252 | } 253 | .single-movie img { 254 | width: 100%; 255 | display: block; 256 | } 257 | .single-movie p { 258 | max-width: 35em; 259 | font-size: 1.2rem; 260 | margin-top: 1.5rem; 261 | line-height: 1.8; 262 | } 263 | .btn { 264 | text-transform: capitalize; 265 | padding: 0.25rem 0.5rem; 266 | background: var(--clr-primary-5); 267 | color: var(--clr-white); 268 | border-radius: var(--radius); 269 | display: inline-block; 270 | margin-top: 0.5rem; 271 | letter-spacing: var(--spacing); 272 | } 273 | @media screen and (min-width: 992px) { 274 | .single-movie { 275 | grid-template-columns: 1fr 2fr; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | import { AppProvider } from './context' 6 | import { BrowserRouter as Router } from 'react-router-dom' 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) 17 | --------------------------------------------------------------------------------