Initial commit

This commit is contained in:
“Henry-Hiles” 2022-10-18 18:58:00 +00:00
commit 7b9f81dd64
20 changed files with 2789 additions and 0 deletions

5
src/App.css Normal file
View file

@ -0,0 +1,5 @@
#root,
.App {
width: 100%;
height: 100%;
}

16
src/App.jsx Normal file
View file

@ -0,0 +1,16 @@
import { Routes, Route } from "react-router-dom"
import "./App.css"
import Home from "pages/Home"
import Movie from "pages/Movie"
const App = () => (
<div className="App">
<Routes>
<Route index element={<Home />} />
<Route path="/movie/:movieId" element={<Movie />} />
<Route path="*" element={<h1>404 - Not Found</h1>} />
</Routes>
</div>
)
export default App

31
src/components/Card.jsx Normal file
View file

@ -0,0 +1,31 @@
import { Link } from "react-router-dom"
import styles from "styles/Card.module.css"
const Card = ({ movie }) => (
<Link to={`/movie/${movie.id}`} className={styles.Link}>
<div className={styles.Card}>
<img
src={movie.posterUrl}
alt={movie.title}
className={styles.Image}
/>
<div className={styles.Bottom}>
<p className={styles.Title}>{movie.title}</p>
<p className={styles.Average}>
{movie.averageVote}{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z" />
</svg>
</p>
</div>
</div>
</Link>
)
export default Card

View file

@ -0,0 +1,12 @@
import Card from "components/Card"
import styles from "styles/CardList.module.css"
const CardList = ({ movies }) => (
<div className={styles.CardList}>
{movies.map((movie) => (
<Card key={movie.id} movie={movie} />
))}
</div>
)
export default CardList

View file

@ -0,0 +1,5 @@
// const Jumbotron = ({ movie }) => (
// )
// export default Jumbotron

12
src/components/TopBar.jsx Normal file
View file

@ -0,0 +1,12 @@
const TopBar = ({ search, setSearch }) => (
<div id="top-bar">
<h1>React Movie Finder</h1>
<input
type="text"
value={search}
onChange={({ target }) => setSearch(target.value)}
/>
</div>
)
export default TopBar

View file

@ -0,0 +1,71 @@
import { createContext, useContext, useEffect, useState } from "react"
import config from "config"
const MoviesContext = createContext()
export const useMovies = () => useContext(MoviesContext)
export const MoviesProvider = ({ children }) => {
const [movies, setMovies] = useState([])
const [genres, setGenres] = useState({})
const [page, setPage] = useState(1)
const [search, setSearch] = useState("")
useEffect(() => {
const run = async () => {
const genresResponse = await fetch(
`https://api.themoviedb.org/3/genre/movie/list?api_key=${config.apiKey}`
)
const genresData = await genresResponse.json()
genresData.genres.forEach((genre) =>
setGenres((current) => {
const copy = {}
Object.assign(copy, current)
copy[genre.id] = genre.name
return copy
})
)
}
run()
})
useEffect(() => {
const run = async () => {
const response = await fetch(
`https://api.themoviedb.org/3/${
search ? "search" : "discover"
}/movie?api_key=${
config.apiKey
}&page=${page}&query=${encodeURIComponent(search)}`
)
const data = await response.json()
setMovies(
data.results.map((movie) => ({
id: movie.id,
overview: movie.overview,
adult: movie.adult,
posterUrl: `https://image.tmdb.org/t/p/w342/${movie.poster_path}`,
backdropUrl: `https://image.tmdb.org/t/p/original/${movie.backdrop_path}`,
genres: movie.genre_ids.map((genreId) => genres[genreId]),
title: movie.title,
releaseDate: movie.release_date,
averageVote: movie.vote_average,
voteCount: movie.vote_count,
popularity: movie.popularity,
}))
)
}
run()
}, [page, genres])
const nextPage = () => setPage((page) => page + 1)
return (
<MoviesContext.Provider
value={[movies, page, search, { nextPage, setSearch }]}
>
{children}
</MoviesContext.Provider>
)
}

30
src/index.css Normal file
View file

@ -0,0 +1,30 @@
:root {
--primary: #14bbaa;
--secondary: #2c3c4c;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
background-color: var(--secondary);
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
height: 100%;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
button {
cursor: pointer;
}

16
src/main.jsx Normal file
View file

@ -0,0 +1,16 @@
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import "./index.css"
import { MoviesProvider } from "contexts/MoviesContext"
import { BrowserRouter } from "react-router-dom"
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<MoviesProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</MoviesProvider>
</React.StrictMode>
)

17
src/pages/Home.jsx Normal file
View file

@ -0,0 +1,17 @@
import { useMovies } from "contexts/MoviesContext"
import CardList from "components/CardList"
import TopBar from "components/TopBar"
import styles from "styles/Home.module.css"
const Home = () => {
const [movies, page, search, { nextPage, setSearch }] = useMovies()
return (
<div className={styles.Container}>
<TopBar search={search} setSearch={setSearch} />
<CardList movies={movies} />
</div>
)
}
export default Home

38
src/pages/Movie.jsx Normal file
View file

@ -0,0 +1,38 @@
import { useEffect, useState } from "react"
import { useParams } from "react-router-dom"
import { useMovies } from "contexts/MoviesContext"
const Movie = () => {
const [movie, setMovie] = useState()
const [movies] = useMovies()
const { movieId } = useParams()
useEffect(
() => setMovie(movies.find((movie) => movie.id === parseInt(movieId))),
[movieId, movies]
)
return (
<div className="movie-jumbo">
<img src={movie?.backdropUrl} alt={movie?.title} className="top" />
<div className="bottom">
<p className="title">{movie?.title}</p>
<p className="average">
{movie?.averageVote}{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
className="bi bi-star-fill"
viewBox="0 0 16 16"
>
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z" />
</svg>
</p>
</div>
</div>
)
}
export default Movie

View file

@ -0,0 +1,27 @@
.Link {
text-decoration: none;
color: white;
}
.Link:hover {
text-decoration: underline;
}
.Image {
width: 100%;
border-radius: 10px;
}
.Card {
background-color: var(--primary);
padding: 12px 15px;
border-radius: 7px;
display: flex;
gap: 5px;
flex-direction: column;
}
.Title,
.Average {
margin: 0;
}

View file

@ -0,0 +1,9 @@
.CardList {
display: grid;
justify-items: center;
width: 85%;
margin: 0 100px;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
text-align: center;
}

View file

@ -0,0 +1,5 @@
.Container {
display: flex;
align-items: center;
flex-direction: column;
}