영화/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