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