import { BrowserRouter, createBrowserRouter, Route, RouterProvider, Routes } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import Login from "./Login";
import MyPage from "./MyPage";
const router = createBrowserRouter();
function App() {
/*
return (
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/info" element={<About />} />
<Route path="/profiles" element={<Profiles />}>
<Route path=":userid" element={<Profile/>} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
*/
return <RouterProvider router={router} />
}
export default App;
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import Profiles from "./Profiles";
import Profile from "./Profile";
import Layout from "./Layout";
import NotFound from "./NotFound";
import Login from "./Login";
import MyPage from "./MyPage";
const router = createBrowserRouter([
{
path: "/", element: <Layout />, children: [
{ path: "/", element: <Home /> },
{ path: "/about", element: <About /> },
{ path: "/info", element: <About /> },
{ path: "/profiles", element: <Profiles />, children: [
{ path: ":userid", element: <Profile /> },
]
},
{ path: "/login", element: <Login /> },
{ path: "/mypage", element: <MyPage /> }
]
},
{path: "*", element: <NotFound/>}
]);
function App() {
return <RouterProvider router={router} />
}
export default App;
영화 제목으로 영화 정보를 조회하는 페이지
조회 결과 목록에서 특정 영화를 선택하면 영화 상세 페이지
영화 포스트, 시놉시스, 동영상 클릭 등을 제공
동영상을 선택하면 유튜브 영상을 실행
{
"page": 1,
"results": [
{
"adult": false, <= 성인영화 여부
"backdrop_path": "/4qCqAdHcNKeAHcK8tJ8wNJZa9cx.jpg", <= 배경 이미지
https://image.tmdb.org/t/p/w500/4qCqAdHcNKeAHcK8tJ8wNJZa9cx.jpg
"genre_ids": [ <= 장르 ID
https://api.themoviedb.org/3/genre/movie/list?api_key=9d2bff12ed955c7f1f74b83187f188ae
12,
28,
878
],
"id": 11, ⇐ 영화 ID
"original_language": "en", ⇐ 언어
"original_title": "Star Wars", ⇐ 제목
"overview": "Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.", ⇐ 시놉시스
"popularity": 132.037,
"poster_path": "/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg", <= 포스터
https://image.tmdb.org/t/p/w500/6FfCtAuVAW8XJjZ7eWeLibRLWTw.jpg
"release_date": "1977-05-25", <= 최초 상영일
"title": "Star Wars", <= 제목
"video": false, <= 비디오 출시 여부
"vote_average": 8.204, <= 평점
"vote_count": 20747 <= 투표수
},
...
https://api.themoviedb.org/3/movie/11/videos?api_key=***
~~~ 영화 ID
{
"id": 11, ⇐ 영화 ID
"results": [
{
"iso_639_1": "en",
"iso_3166_1": "US",
"name": "Star Wars (1977) - Trailer", ⇐ 비디오 클립 제목
"key": "XsS1yE2f-hE", ⇐ 비디오 클립 식별자
"site": "YouTube", ⇐ 비디오 클립 제공 사이트
"size": 1080,
"type": "Trailer",
"official": false,
"published_at": "2022-05-25T16:00:29.000Z",
"id": "63d1c26ecb71b800810237b7"
},
...
유튜브에 key(XsS1yE2f-hE 등)를 넣어서 소스 코드 받아오는 API가 있다!
react-youtube 컴포넌트를 이용
<YouTube videoid="XsS1yE2f-hE" />
npm install axios react-youtube
import { useState } from 'react';
import axios from 'axios';
export default function MovieList() {
const [title, setTitle] = useState('');
const searchMovie = () => {
const endpoint = `https://api.themoviedb.org/3/search/movie?api_key=9d2bff12ed955c7f1f74b83187f188ae&query=${title}`;
axios
.get(endpoint)
.then((res) => console.log(res))
.catch((err) => console.log(err));
};
return (
<div>
<h1>영화 조회</h1>
<div>
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
<button onClick={searchMovie}>조회</button>
</div>
</div>
);
}
{ path: '/movieList', element: <MovieList /> },
<li><Link to="/movieList">영화조회</Link></li>
import { useState } from 'react';
import axios from 'axios';
import './MovieList.css';
export default function MovieList() {
const [title, setTitle] = useState('');
const [movies, setMovies] = useState([]);
const [isSearch, setIsSearch] = useState(false);
const searchMovie = () => {
setIsSearch(true);
const endpoint = `https://api.themoviedb.org/3/search/movie?api_key=9d2bff12ed955c7f1f74b83187f188ae&query=${title}`;
axios
.get(endpoint)
.then((res) => {
console.log(res);
setMovies(res.data.results);
})
.catch((err) => console.log(err));
};
return (
<div className="container">
<h1>영화 조회</h1>
<div className="search">
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} />
<button onClick={searchMovie}>조회</button>
</div>
{isSearch && movies.length === 0 && <div>일치하는 영화가 존재하지 않습니다.</div>}
{!isSearch && <div>검색할 영화 제목을 입력하세요.</div>}
{movies.length !== 0 &&
movies.map((m) => (
<div className="movie">
<div className="poster">
<img src={`https://image.tmdb.org/t/p/w500${m.poster_path}`} />
</div>
<div className="text">
<h1>{m.title}</h1>
<p>{m.overview}</p>
</div>
</div>
))}
</div>
);
}
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
// http://localhost:3000/movieDetail/:movieid
export default function MovieDetail() {
const params = useParams();
const movieid = params.movieid;
// 컴포넌트가 마운트되었을 때 비디오 클립을 조회
useEffect(() => {
const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
axios
.get(endpoint)
.then((res) => console.log(res))
.catch((err) => console.log(err));
}, []);
return (
<>
<h1>데이터를 가져오고 있습니다.</h1>
</>
);
}
{ path: '/movieDetail/:movieid', element: <MovieDetail /> },
<h1>
<Link to={`/movieDetail/${m.id}`}>{m.title}</Link>
</h1>
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
// http://localhost:3000/movieDetail/:movieid
export default function MovieDetail() {
const params = useParams();
const movieid = params.movieid;
const [videos, setVideos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
// 컴포넌트가 마운트되었을 때 비디오 클립을 조회
useEffect(() => {
const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
axios
.get(endpoint)
.then((res) => {
console.log(res);
setVideos(res.data.results);
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
return (
<div>
{isLoading && <h1>데이터를 가져오고 있습니다.</h1>}
{!isLoading && videos.length === 0 && <h1>등록된 비디오 클립이 존재하지 않습니다.</h1>}
{!isLoading &&
videos.length !== 0 &&
videos.map((v) => (
<div>
{v.name} (출시일: {v.published_at.substring(0, 10)})
</div>
))}
</div>
);
}
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
import YouTube from 'react-youtube';
const opts = {
height: '390',
width: '640',
playerVars: {
autoplay: 1,
},
};
export default function MovieDetail() {
const params = useParams();
const movieid = params.movieid;
const [videos, setVideos] = useState([]);
const [isLoading, setIsLoading] = useState(true);
// 컴포넌트가 마운트되었을 때 비디오 클립을 조회
useEffect(() => {
const endpoint = `https://api.themoviedb.org/3/movie/${movieid}/videos?api_key=9d2bff12ed955c7f1f74b83187f188ae`;
axios
.get(endpoint)
.then((res) => {
console.log(res);
setVideos(res.data.results);
setIsLoading(false);
})
.catch((err) => console.log(err));
}, []);
const [key, setKey] = useState('');
return (
<>
<div>
{isLoading && <h1>데이터를 가져오고 있습니다.</h1>}
{!isLoading && videos.length === 0 && <h1>등록된 비디오 클립이 존재하지 않습니다.</h1>}
{!isLoading &&
videos.length !== 0 &&
videos.map((v) => (
<div style={{ fontSize: 20 }} onClick={() => setKey(v.key)}>
{v.name} (출시일: {v.published_at.substring(0, 10)})
</div>
))}
</div>
{key && <YouTube videoId={key} opts={opts} />}
</>
);
}
🔑
마운트되는 순간 axios.get해서 자동으로 가져오게 하기 위해 useEffect를 사용하고 의존성 배열에 빈 배열로 해서 자동으로 가져온다.
나는 http://api.tvmaze.com/search/shows?q=${title}
API를 사용해서 TVMaze 컴포넌트를 만들었다 !
이렇게 아무것도 검색하지 않았을 때 어떤 API를 사용했는지, 응답 데이터 구조가 어떤지 나타냈다.
이렇게 hi라고 검색해서 조회를 누르면 api 쿼리스트링에 hi 라고 들어가 api 요청을 한다.
image.original로 화질이 image.medium보다 괜찮은 포스터를 불러와 좌측에 띄우고 우측엔 프로그램 정보를 제공했다.
모두 비어있는 값일 때는 00 정보가 없습니다
라고 처리를 하였다.
장르는 배열로 여러 개 들어오기도 해서 |
로 구분자를 지었다.
요약 부분이 길다면 높이를 지정하고 스크롤 가능하게 만들어 조금 더 깔끔한 UI를 보여주려고 했다.
조에서 모든 사람의 컴포넌트를 병합하는 과정에서 css를 전역적으로 설정하신 분도 계셔서 최대한 겹치지 않게 병합하려고 다른 분의 코드에 className을 붙이며 수정하였다.
Navbar는 이런식으로 만들었다. 000은 해당 컴포넌트를 만든 사람 이름이 들어간다 !
hover하면 scale을 줘 조금 더 커진다.
시간이 더 있었더라면 좀 더 잘 만들었을텐데 Navbar 만드랴 다른 사람 컴포넌트 합치랴 수정하랴 내 컴포넌트 만드랴 소통하랴 너무너무 바빴던 것 같다.
쉬는 시간 하나 없이 계속했던 것 같다.
Navbar와 App.js 라우팅 설정 및 컴포넌트 이름, url 이름 등을 미리 세팅해서 나중에 소스 코드만 받아 붙여넣을 수 있도록 하였다.
API key를 발급 받으신 분도 계셔서 .env 파일에 담아 유출되지 않도록 하였다.
11명의 컴포넌트를 시간 내에 다 병합해야 하는데 비대면이기 때문에 소통이 바로바로 안 되면 불편할까봐 소통도 열심히 하려고 바삐 움직였다..!
전체적으로 보면 이런 느낌 !
시간 관계 상 다음 날 발표를 하였고, 강사님께 UI가 예쁘다고 칭찬받았다 ㅎ..ㅎ
마지막까지 더 나은 결과물을 보여드리기 위해 계속해서 수정했던 우리팀 아쥬 칭찬해 👏👏