Skip to content

Commit 0362944

Browse files
committed
3단계 시작, 검색기능 구현중
1 parent 38bede6 commit 0362944

File tree

15 files changed

+224
-117
lines changed

15 files changed

+224
-117
lines changed

src/App.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { BrowserRouter, Routes, Route } from "react-router-dom";
22

33
import Layout from "./Layout.jsx";
4-
import MainPage from "./page/MainPage.jsx";
5-
import DetailPage from "./page/DetailPage.jsx";
4+
import MainPage from "./pages/MainPage.jsx";
5+
import DetailPage from "./pages/DetailPage.jsx";
66

77
import { createGlobalStyle } from "styled-components";
88

src/Component/NavigationBarComponent.jsx

Lines changed: 0 additions & 81 deletions
This file was deleted.

src/Layout.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Outlet } from "react-router-dom";
2-
import NavigationBar from "./component/NavigationBarComponent";
2+
import NavigationBar from "./components/NavigationBar.jsx";
33

44
export default function Layout() {
55
return (

src/Component/CardComponent.jsx renamed to src/components/Card.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { Link } from "react-router-dom";
33

44
// <-------------------- function, return -------------------->
55

6-
export default function CardComponent({ movie }) {
6+
export default function Card({ movie }) {
77
return (
8-
<Card>
8+
<Container>
99
<Link to={`/details/${movie.id}`}>
1010
<Poster
1111
src={`https://image.tmdb.org/t/p/w500${movie.poster_path}`}
@@ -16,13 +16,13 @@ export default function CardComponent({ movie }) {
1616
<Title>{movie.title}</Title>
1717
<Rating>{movie.vote_average}</Rating>
1818
</Info>
19-
</Card>
19+
</Container>
2020
);
2121
}
2222

2323
// <-------------------- styled-components -------------------->
2424

25-
const Card = styled.div`
25+
const Container = styled.div`
2626
width: 18vw;
2727
max-width: 200px;
2828
min-width: 150px;

src/components/NavigationBar.jsx

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import styled from "styled-components";
2+
import { Link, useSearchParams } from "react-router-dom";
3+
import { useEffect, useState } from "react";
4+
5+
import useDebounce from "../data/hooks/useDebounce.js";
6+
import useTmdbKeywordData from "../data/hooks/useTmdbKeywordData.js";
7+
8+
// <-------------------- function -------------------->
9+
10+
export default function NavigationBar() {
11+
const [searchParams, setSearchParams] = useSearchParams();
12+
const keywordParam = searchParams.get("keyword") || "";
13+
const [keyword, setKeyword] = useState(keywordParam);
14+
15+
const debouncedKeyword = useDebounce(keyword, 500);
16+
const searchKeyword = useTmdbKeywordData(debouncedKeyword);
17+
18+
useEffect(() => {
19+
if (keyword.trim() === "") {
20+
searchParams.delete("keyword");
21+
setSearchParams(searchParams);
22+
} else {
23+
setSearchParams({ keyword });
24+
}
25+
}, [keyword]);
26+
27+
// <-------------------- return -------------------->
28+
29+
return (
30+
<Navigationbar>
31+
<Link to="/" style={{ textDecoration: "none", color: "white" }}>
32+
<Logo>🎬 • WISH MOVIE</Logo>
33+
</Link>
34+
35+
<SearchBox>
36+
<input
37+
type="text"
38+
placeholder="tell me your wish 🧞‍♂️"
39+
value={keyword}
40+
onChange={(e) => setKeyword(e.target.value)}
41+
/>
42+
</SearchBox>
43+
44+
{/* <-------------------- 검색 결과 표시 */}
45+
46+
{searchKeyword && searchKeyword.length > 0 && (
47+
<searchKeyword>
48+
{searchKeyword.map((movie) => (
49+
<p key={movie.id}>{movie.title}</p>
50+
))}
51+
</searchKeyword>
52+
)}
53+
54+
<Buttons>
55+
<LoginBtn>로그인</LoginBtn>
56+
<SignupBtn>회원가입</SignupBtn>
57+
</Buttons>
58+
</Navigationbar>
59+
);
60+
}
61+
62+
// <-------------------- styled-components -------------------->
63+
64+
const Navigationbar = styled.nav`
65+
width: 100%;
66+
height: 60px;
67+
background-color: #1a1a1a;
68+
color: white;
69+
display: flex;
70+
align-items: center;
71+
justify-content: space-between;
72+
padding: 0 40px;
73+
box-sizing: border-box;
74+
overflow: hidden;
75+
gap: 50px;
76+
`;
77+
78+
const Logo = styled.h1`
79+
font-size: 24px;
80+
display: flex;
81+
align-items: center;
82+
flex-shrink: 0;
83+
color: #757575;
84+
`;
85+
86+
const SearchBox = styled.div`
87+
flex: 1;
88+
display: flex;
89+
justify-content: center;
90+
input {
91+
width: 100%;
92+
height: 30px;
93+
border-radius: 15px;
94+
border: none;
95+
padding: 0 15px;
96+
font-size: 16px;
97+
background-color: #bafd00;
98+
}
99+
`;
100+
101+
const SearchResults = styled.div`
102+
position: absolute;
103+
top: 70px;
104+
background-color: #111;
105+
width: 300px;
106+
max-height: 400px;
107+
overflow-y: auto;
108+
color: white;
109+
border-radius: 8px;
110+
padding: 10px;
111+
`;
112+
113+
const Buttons = styled.div`
114+
display: flex;
115+
align-items: center;
116+
gap: 12px;
117+
flex-shrink: 0;
118+
`;
119+
120+
const LoginBtn = styled.button`
121+
background: none;
122+
color: white;
123+
border: 1px solid white;
124+
border-radius: 15px;
125+
padding: 5px 15px;
126+
cursor: pointer;
127+
`;
128+
129+
const SignupBtn = styled.button`
130+
background-color: white;
131+
color: #141414;
132+
border: none;
133+
border-radius: 15px;
134+
padding: 5px 15px;
135+
cursor: pointer;
136+
`;
File renamed without changes.
File renamed without changes.

src/data/hooks/useDebounce.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useEffect, useState } from "react";
2+
3+
export default function useDebounce(value, delay) {
4+
const [debouncedValue, setDebouncedValue] = useState(value);
5+
6+
useEffect(() => {
7+
const handler = setTimeout(() => {
8+
setDebouncedValue(value);
9+
}, delay);
10+
11+
return () => {
12+
clearTimeout(handler);
13+
};
14+
}, [value, delay]);
15+
16+
return debouncedValue;
17+
}

src/Api/DetailApi.js renamed to src/data/hooks/useTmdbDetailData.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ const apiToken = import.meta.env.VITE_TMDB_ACCESS_TOKEN;
55

66
// <-------------------- function -------------------->
77

8-
export default function useDetailApi(id) {
9-
const [detailApi, setDetailApi] = useState([]);
10-
// console.log(movieId);
8+
export default function useTmdbDetailData(id) {
9+
const [tmdbDetailData, setTmdbDetailData] = useState([]);
1110

1211
// <-------------------- API : Details
1312

@@ -25,12 +24,12 @@ export default function useDetailApi(id) {
2524
axios
2625
.request(options)
2726
.then((res) => {
28-
setDetailApi(res.data);
27+
setTmdbDetailData(res.data);
2928
})
3029
.catch((err) => console.error(err));
3130
}, []);
3231

3332
// <-------------------- return -------------------->
3433

35-
return detailApi;
34+
return tmdbDetailData;
3635
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useEffect, useState } from "react";
2+
import axios from "axios";
3+
4+
const apiToken = import.meta.env.VITE_TMDB_ACCESS_TOKEN;
5+
6+
export default function useTmdbKeywordData(keyword) {
7+
const [search, setSearch] = useState([]);
8+
9+
useEffect(() => {
10+
if (!keyword) return;
11+
12+
const options = {
13+
method: "GET",
14+
url: `https://api.themoviedb.org/3/search/movie`,
15+
params: {
16+
query: keyword,
17+
language: "ko-KR",
18+
page: 1,
19+
},
20+
headers: {
21+
accept: "application/json",
22+
Authorization: `Bearer ${apiToken}`,
23+
},
24+
};
25+
26+
axios
27+
.request(options)
28+
.then((res) => setSearch(res.data.results))
29+
.catch((err) => console.error(err));
30+
}, [keyword]);
31+
32+
return search;
33+
}

0 commit comments

Comments
 (0)