diff --git a/src/App.jsx b/src/App.jsx index 08ed6b2a..4bc80fe3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,8 +1,8 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import Layout from "./Layout.jsx"; -import MainPage from "./page/MainPage.jsx"; -import DetailPage from "./page/DetailPage.jsx"; +import MainPage from "./pages/MainPage.jsx"; +import DetailPage from "./pages/DetailPage.jsx"; import { createGlobalStyle } from "styled-components"; diff --git a/src/Layout.jsx b/src/Layout.jsx index 20ec962d..df6005bd 100644 --- a/src/Layout.jsx +++ b/src/Layout.jsx @@ -1,10 +1,8 @@ import { Outlet } from "react-router-dom"; -import NavigationBar from "./component/NavigationBarComponent"; export default function Layout() { return (
-
diff --git a/src/assets/react.svg b/src/images/react.svg similarity index 100% rename from src/assets/react.svg rename to src/images/react.svg diff --git a/src/Page/DetailPage.jsx b/src/pages/DetailPage.jsx similarity index 63% rename from src/Page/DetailPage.jsx rename to src/pages/DetailPage.jsx index d3fba3fa..12b916d7 100644 --- a/src/Page/DetailPage.jsx +++ b/src/pages/DetailPage.jsx @@ -1,33 +1,38 @@ import styled from "styled-components"; +import NavigationBar from "./components/NavigationBar"; + import { useParams } from "react-router-dom"; -import useDetailApi from "../Api/DetailApi"; +import useTmdbDetailData from "./data/hooks/useTmdbDetailData"; // <-------------------- function --------------------> export default function DetailPage() { const { id } = useParams(); - const detailApi = useDetailApi(id); + const tmdbDetail = useTmdbDetailData(id); // <-------------------- return --------------------> return ( - - - - - {detailApi.title} - {detailApi.vote_average} - - {detailApi.overview} - - + <> + + + + + + {tmdbDetail.title} + {tmdbDetail.vote_average} + + {tmdbDetail.overview} + + + ); } diff --git a/src/Page/MainPage.jsx b/src/pages/MainPage.jsx similarity index 66% rename from src/Page/MainPage.jsx rename to src/pages/MainPage.jsx index 4f8c01a5..7b983c1e 100644 --- a/src/Page/MainPage.jsx +++ b/src/pages/MainPage.jsx @@ -1,8 +1,9 @@ import styled from "styled-components"; +import Card from "./components/Card.jsx"; -import CardComponent from "../Component/CardComponent.jsx"; -import useTopApi from "../Api/TopApi.js"; -import useMainApi from "../Api/MainApi"; +import useTmdbTopData from "./data/hooks/useTmdbTopData.js"; +import useTmdbMainData from "./data/hooks/useTmdbMainData.js"; +import useTmdbKeywordData from "./data/hooks/useTmdbKeywordData.js"; import { Swiper, SwiperSlide } from "swiper/react"; import { Navigation, Pagination, Autoplay } from "swiper/modules"; @@ -10,18 +11,28 @@ import "swiper/css"; import "swiper/css/navigation"; import "swiper/css/pagination"; +import { useSearchParams } from "react-router-dom"; +import NavigationBar from "./components/NavigationBar.jsx"; + // <-------------------- function --------------------> export default function MainPage() { - const topApi = useTopApi(); - const mainApi = useMainApi(); + const [searchParams] = useSearchParams(); + const query = searchParams.get("keyword")?.trim(); + + const tmdbTop = useTmdbTopData(); + const tmdbMainData = useTmdbMainData(); + const tmdbKeywordData = useTmdbKeywordData(query); + + const tmdbData = query ? tmdbKeywordData : tmdbMainData; // <-------------------- return --------------------> return ( <> + - TOP 10 πŸ† + πŸ† TOP 10 - {topApi.slice(0, 10).map((api) => ( + {tmdbTop.slice(0, 10).map((api) => ( - + ))} - Popular ✨ - - {mainApi.map((api) => ( - + ✨ Popular + + {tmdbData.map((movie) => ( + ))} - + ); @@ -69,7 +80,7 @@ const Popular = styled.p` padding-left: 100px; `; -const Mainapi = styled.div` +const MainList = styled.div` display: flex; flex-wrap: wrap; justify-content: center; diff --git a/src/Component/CardComponent.jsx b/src/pages/components/Card.jsx similarity index 91% rename from src/Component/CardComponent.jsx rename to src/pages/components/Card.jsx index db059c4e..b0d07721 100644 --- a/src/Component/CardComponent.jsx +++ b/src/pages/components/Card.jsx @@ -3,9 +3,9 @@ import { Link } from "react-router-dom"; // <-------------------- function, return --------------------> -export default function CardComponent({ movie }) { +export default function Card({ movie }) { return ( - + {movie.title} {movie.vote_average} - + ); } // <-------------------- styled-components --------------------> -const Card = styled.div` +const Container = styled.div` width: 18vw; max-width: 200px; min-width: 150px; diff --git a/src/Component/NavigationBarComponent.jsx b/src/pages/components/NavigationBar.jsx similarity index 52% rename from src/Component/NavigationBarComponent.jsx rename to src/pages/components/NavigationBar.jsx index b9e10f1e..64e086c1 100644 --- a/src/Component/NavigationBarComponent.jsx +++ b/src/pages/components/NavigationBar.jsx @@ -1,14 +1,45 @@ import styled from "styled-components"; -import { Link } from "react-router-dom"; +import { Link, useSearchParams } from "react-router-dom"; +import { useEffect, useState } from "react"; + +import useDebounce from "./hooks/useDebounce.js"; +import useTmdbKeywordData from "../data/hooks/useTmdbKeywordData.js"; + +// <-------------------- function --------------------> export default function NavigationBar() { + const [searchParams, setSearchParams] = useSearchParams(); + const keywordParam = searchParams.get("keyword") || ""; + const [keyword, setKeyword] = useState(keywordParam); + + const debouncedKeyword = useDebounce(keyword, 100); + useTmdbKeywordData(debouncedKeyword); + + useEffect(() => { + if (keyword.trim() === "") { + const params = new URLSearchParams(searchParams); + params.delete("keyword"); + setSearchParams(params); + } else { + setSearchParams({ keyword }); + } + }, [keyword, searchParams, setSearchParams]); + + // <-------------------- return --------------------> + return ( 🎬 β€’ WISH MOVIE + - + setKeyword(e.target.value)} + /> 둜그인 @@ -18,6 +49,8 @@ export default function NavigationBar() { ); } +// <-------------------- styled-components --------------------> + const Navigationbar = styled.nav` width: 100%; height: 60px; @@ -55,6 +88,18 @@ const SearchBox = styled.div` } `; +const SearchResults = styled.div` + position: absolute; + top: 70px; + background-color: #111; + width: 300px; + max-height: 400px; + overflow-y: auto; + color: white; + border-radius: 8px; + padding: 10px; +`; + const Buttons = styled.div` display: flex; align-items: center; diff --git a/src/pages/components/hooks/useDebounce.js b/src/pages/components/hooks/useDebounce.js new file mode 100644 index 00000000..87659778 --- /dev/null +++ b/src/pages/components/hooks/useDebounce.js @@ -0,0 +1,17 @@ +import { useEffect, useState } from "react"; + +export default function useDebounce(value, delay) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/src/Data/DetailData.json b/src/pages/data/DummyDetailData.json similarity index 100% rename from src/Data/DetailData.json rename to src/pages/data/DummyDetailData.json diff --git a/src/Data/MainData.json b/src/pages/data/DummyMainData.json similarity index 100% rename from src/Data/MainData.json rename to src/pages/data/DummyMainData.json diff --git a/src/Api/DetailApi.js b/src/pages/data/hooks/useTmdbDetailData.js similarity index 73% rename from src/Api/DetailApi.js rename to src/pages/data/hooks/useTmdbDetailData.js index d9ade3a3..33d5546c 100644 --- a/src/Api/DetailApi.js +++ b/src/pages/data/hooks/useTmdbDetailData.js @@ -1,13 +1,12 @@ import { useEffect, useState } from "react"; import axios from "axios"; -const apiToken = import.meta.env.VITE_TMDB_ACCESS_TOKEN; +const apiToken = import.meta.env.VITE_TMDB_API_KEY; // <-------------------- function --------------------> -export default function useDetailApi(id) { - const [detailApi, setDetailApi] = useState([]); - // console.log(movieId); +export default function useTmdbDetailData(id) { + const [tmdbDetailData, setTmdbDetailData] = useState([]); // <-------------------- API : Details @@ -25,12 +24,12 @@ export default function useDetailApi(id) { axios .request(options) .then((res) => { - setDetailApi(res.data); + setTmdbDetailData(res.data); }) .catch((err) => console.error(err)); }, []); // <-------------------- return --------------------> - return detailApi; + return tmdbDetailData; } diff --git a/src/pages/data/hooks/useTmdbKeywordData.js b/src/pages/data/hooks/useTmdbKeywordData.js new file mode 100644 index 00000000..78be6761 --- /dev/null +++ b/src/pages/data/hooks/useTmdbKeywordData.js @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; +import axios from "axios"; + +const apiToken = import.meta.env.VITE_TMDB_API_KEY; + +export default function useTmdbKeywordData(keyword) { + const [search, setSearch] = useState([]); + + useEffect(() => { + console.log(keyword); + + if (!keyword) return; + + const options = { + method: "GET", + url: `https://api.themoviedb.org/3/search/movie`, + params: { + query: keyword, + language: "ko-KR", + page: 1, + }, + headers: { + accept: "application/json", + Authorization: `Bearer ${apiToken}`, + }, + }; + + axios + .request(options) + .then((res) => setSearch(res.data.results)) + .catch((err) => console.error(err)); + }, [keyword]); + + return search; +} diff --git a/src/Api/MainApi.js b/src/pages/data/hooks/useTmdbMainData.js similarity index 77% rename from src/Api/MainApi.js rename to src/pages/data/hooks/useTmdbMainData.js index e8385c3f..53b8ca55 100644 --- a/src/Api/MainApi.js +++ b/src/pages/data/hooks/useTmdbMainData.js @@ -2,13 +2,13 @@ import { useEffect, useState } from "react"; // <-------------------- function --------------------> -export default function useMainApi() { - const [mainApi, setMainApi] = useState([]); +export default function useTmdbMainData(keyword) { + const [tmdbMainData, setTmdbMainData] = useState([]); // <-------------------- API : popular useEffect(() => { - const apiToken = import.meta.env.VITE_TMDB_ACCESS_TOKEN; + const apiToken = import.meta.env.VITE_TMDB_API_KEY; const options = { method: "GET", headers: { @@ -27,12 +27,12 @@ export default function useMainApi() { (resData) => resData.adult === false ); // console.log("βœ… TMDB 응닡 데이터:", filteredTmdbMovies); - setMainApi(noAdultResData); + setTmdbMainData(noAdultResData); }) .catch((err) => console.error(err)); - }, []); + }, [keyword]); // <-------------------- return --------------------> - return mainApi; + return tmdbMainData; } diff --git a/src/Api/TopApi.js b/src/pages/data/hooks/useTmdbTopData.js similarity index 73% rename from src/Api/TopApi.js rename to src/pages/data/hooks/useTmdbTopData.js index cf1b02c5..fb78954f 100644 --- a/src/Api/TopApi.js +++ b/src/pages/data/hooks/useTmdbTopData.js @@ -1,12 +1,12 @@ import { useEffect, useState } from "react"; import axios from "axios"; -const apiToken = import.meta.env.VITE_TMDB_ACCESS_TOKEN; +const apiToken = import.meta.env.VITE_TMDB_API_KEY; // <-------------------- function --------------------> -export default function useTopApi() { - const [topApi, setTopApi] = useState([]); +export default function useTmdbTopData() { + const [tmdbTopData, setTmdbTopData] = useState([]); // <-------------------- API : Top @@ -24,12 +24,12 @@ export default function useTopApi() { axios .request(options) .then((res) => { - setTopApi(res.data.results); + setTmdbTopData(res.data.results); }) .catch((err) => console.error(err)); - }); + },[]); // <-------------------- return --------------------> - return topApi; + return tmdbTopData; }