영화/ReactJS
14. Practice Movie App - Styles
본시모니
2025. 4. 9. 17:50
Movie.js
import PropTypes from "prop-types"; //prop-types를 사용하기 위해서는 터미널에서 npm i prop-types를 실행해야 한다!
import {Link} from "react-router-dom";
import styles from "./Movie.module.css";
function Movie({id, coverImg, title, year, summary, genres}){
return(
<div>
<div className={styles.movie}>
<img src={coverImg} alt={title} className={styles.movie__img} />
<h2 className={styles.movie__title}>
<Link to={`/movie/${id}`}>{title}</Link>
</h2>
<h3 className={styles.movie__year}>{year}</h3>
<p>{summary.length > 235 ? `${summary.slice(0, 235)}...` : summary}</p>
<ul className={styles.movie__genres}>
{genres.map((g) =>
(<li>{g}</li>)
)}
</ul>
</div>
</div>
);
};
Movie.prototype = {
id : PropTypes.number.isRequired,
coverImg : PropTypes.string.isRequired,
title : PropTypes.string.isRequired,
summary : PropTypes.string.isRequired,
genres : PropTypes.arrayOf(PropTypes.string).isRequired
}
export default Movie;
Movie.module.js
.movie {
background-color: white;
margin-bottom: 70px;
font-weight: 300;
padding: 20px;
border-radius: 5px;
color: #adaeb9;
display: grid;
grid-template-columns: minmax(150px, 1fr) 2fr;
grid-gap: 20px;
text-decoration: none;
color: inherit;
box-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25),
0 8px 16px -8px rgba(0, 0, 0, 0.3), 0 -6px 16px -6px rgba(0, 0, 0, 0.025);
}
.movie__img {
position: relative;
top: -50px;
max-width: 150px;
width: 100%;
margin-right: 30px;
box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25),
0 18px 36px -18px rgba(0, 0, 0, 0.3), 0 -12px 36px -8px rgba(0, 0, 0, 0.025);
}
.movie__title,
.movie__year {
margin: 0;
font-weight: 300;
text-decoration: none;
}
.movie__title a {
margin-bottom: 5px;
font-size: 24px;
color: #2c2c2c;
text-decoration: none;
}
.movie__genres {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
margin: 5px 0px;
}
.movie__genres li,
.movie__year {
margin-right: 10px;
font-size: 14px;
}
Home.js
import { useEffect, useState } from 'react';
import Movie from "../components/Movie";
import styles from "./Home.module.css";
function Home(){
const [loading, setLoading] = useState(true);
const [movies, setMovies] = useState([]);
const getMovies = async() => {
const json = await(await fetch(`https://yts.mx/api/v2/list_movies.json?minimum_ration=8.8&sort_by=year`)).json();
setMovies(json.data.movies);
setLoading(false);
};
useEffect(()=>{
//fetch 대신에 요즘 async-await를 사용한다.
/*
fetch(`https://yts.mx/api/v2/list_movies.json?minimum_ration=8.5&sort_by=year`)
.then(response => {
response.json()
.then((json) => {
setMovies(json.data.movies)
setLoading(false);
});
});
*/
getMovies();
}, []);
return (
<div className={styles.container}>
{
loading ? (
<div className={styles.loader}>
<span>Loading...</span>
</div>
)
: (
<div className={styles.movies}>
{movies.map((movie) => (
<Movie
key = {movie.id}
id = {movie.id}
year={movie.year}
coverImg = {movie.medium_cover_image}
title = {movie.title}
summary = {movie.summary}
genres = {movie.genres}
/>
))}
</div>
)
}
</div>
);
};
export default Home;
Home.module.css
.container {
height: 100%;
display: flex;
justify-content: center;
}
.loader {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-weight: 300;
}
.movies {
display: grid;
grid-template-columns: repeat(2, minmax(400px, 1fr));
grid-gap: 100px;
padding: 50px;
width: 80%;
padding-top: 70px;
}
@media screen and (max-width: 1090px) {
.movies {
grid-template-columns: 1fr;
width: 100%;
}
}
App.js
import {
BrowserRouter as Router,
Route,
Routes
} from "react-router-dom";
import Home from "./routes/Home";
import Detail from "./routes/Detail";
function App() {
// 누군가 만들어 놓은 컴포넌트를 잘 사용하면 된다.
// Swith(Routes)가 하는 역할은 Route를 찾는 것인데 Route는 URL을 의미한다.
// 사용자가 "/" 경로에 있으면 Home Route를 랜더링한다.
return <Router>
<Routes>
<Route path = "/React-practice-movie-app/movie/:id" element={<Detail />} />
<Route path = "/React-practice-movie-app" element={<Home />} />
</Routes>
</Router>;
}
export default App;
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import "./styles.css";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
index.css
body {
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;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
style.css
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
background-color: #eff3f7;
height: 100%;
}
Detail.js
import { useEffect } from "react";
import { useParams } from "react-router-dom";
function Detail(){
const {id} = useParams();
const getMovies = async () => {
const json = await(await fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${id}`)).json();
};
useEffect(()=>{
getMovies();
},[]);
return <h1>Detail</h1>
};
export default Detail;
package.json
{
"name": "practice-movie-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"gh-pages": "^6.3.0",
"prop-types": "^15.8.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.5.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"deploy": "gh-pages -d build",
"predeploy": "npm run build"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "https://bonsimony.github.io/React-practice-movie-app"
}
practice-movie-app.z01
19.53MB
practice-movie-app.z02
19.53MB
practice-movie-app.z03
19.53MB
practice-movie-app.z04
19.53MB
practice-movie-app.zip
6.32MB