Skip to content

MOVIE 프로젝트 레이아웃 완성 / API 연결 / 라우팅 처리 #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,550 changes: 3,550 additions & 0 deletions ben/week2/movie/package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion ben/week2/movie/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.27.0",
"styled-components": "^6.1.13"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
Expand Down
12 changes: 12 additions & 0 deletions ben/week2/movie/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* 전역 스타일이 필요한 경우 여기에 추가 */
body {
margin: 0;
padding: 0;
background-color: #141414;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

* {
box-sizing: border-box;
}
39 changes: 25 additions & 14 deletions ben/week2/movie/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { MOVIES } from './mock/movies'
import './index.css'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import RootLayout from './layouts/RootLayout';
import MoviesPage from './pages/MoviesPage';
import MovieList from './pages/MovieList';
import Login from './pages/Login';
import Movies from './pages/Movies';
import Signup from './pages/Signup';
import Search from './pages/Search';

function App() {
const baseUrl = 'https://image.tmdb.org/t/p/w500'

return (
<div className='movie-list'>
{/* Movie List */}
{MOVIES.results.map((movie) => (
<div key={movie.id} className='movie-card'>
<img src={`${baseUrl}${movie.poster_path}`} alt="${movie.title}" className='movie-poster'/>
</div>
))}
</div>
)
<Router>
<Routes>
<Route path="/" element={<RootLayout />}>
<Route index element={<MoviesPage />} />
<Route path="movies" element={<Movies />} />
<Route path="movies/now-playing" element={<MovieList category="now_playing" />} />
<Route path="movies/popular" element={<MovieList category="popular" />} />
<Route path="movies/top-rated" element={<MovieList category="top_rated" />} />
<Route path="movies/upcoming" element={<MovieList category="upcoming" />} />
<Route path="search" element={<Search />} />
<Route path="login" element={<Login />} />
<Route path="signup" element={<Signup />} />
</Route>
</Routes>
</Router>
);
}

export default App
export default App;
17 changes: 17 additions & 0 deletions ben/week2/movie/src/components/Card/card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import * as S from './card.style';

const Card = ({ movie }) => {
const baseUrl = 'https://image.tmdb.org/t/p/w500';

return (
<S.CardWrapper>
<S.MoviePoster src={`${baseUrl}${movie.poster_path}`} alt={movie.title} />
<S.Overlay>
<S.Title>{movie.title}</S.Title>
</S.Overlay>
</S.CardWrapper>
);
};

export default Card;
41 changes: 41 additions & 0 deletions ben/week2/movie/src/components/Card/card.style.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import styled from 'styled-components';

export const CardWrapper = styled.div`
position: relative;
width: 200px;
height: 300px;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
`;

export const MoviePoster = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
`;

export const Overlay = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;

&:hover {
opacity: 1;
}
`;

export const Title = styled.h3`
color: white;
text-align: center;
padding: 10px;
`;
50 changes: 50 additions & 0 deletions ben/week2/movie/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';

const Nav = styled.nav`
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background-color: #1a1a1a;
`;

const Logo = styled(Link)`
color: #ff0000;
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
`;

const ButtonContainer = styled.div`
display: flex;
gap: 1rem;
`;

const StyledButton = styled(Link)`
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
background-color: #333;
color: white;
text-decoration: none;
transition: background-color 0.3s;

&:hover {
background-color: #ff0000;
}
`;

const Navbar = () => {
return (
<Nav>
<ButtonContainer>
<StyledButton to="/login">로그인</StyledButton>
<StyledButton to="/signup">회원가입</StyledButton>
</ButtonContainer>
</Nav>
);
};

export default Navbar;
79 changes: 79 additions & 0 deletions ben/week2/movie/src/components/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { BiSearch } from 'react-icons/bi';
import { MdLocalMovies } from 'react-icons/md';

const SidebarContainer = styled.div`
width: 250px;
height: 100vh;
background-color: #1a1a1a;
position: fixed;
left: 0;
top: 0;
padding: 20px;
color: white;
`;

const Logo = styled.h1`
color: #E51013;
margin-bottom: 40px;
cursor: pointer;

&:hover {
opacity: 0.8;
}
`;

const NavButton = styled.button`
display: flex;
align-items: center;
gap: 10px;
background: none;
border: none;
color: white;
font-size: 16px;
padding: 10px;
width: 100%;
cursor: pointer;

&:hover {
background-color: rgba(255, 255, 255, 0.1);
}

svg {
font-size: 20px;
}
`;

const Sidebar = () => {
const navigate = useNavigate();

const handleLogoClick = () => {
navigate('/');
};

const handleMoviesClick = () => {
navigate('/movies');
};

const handleSearchClick = () => {
navigate('/search');
};

return (
<SidebarContainer>
<Logo onClick={handleLogoClick}>YONGCHA</Logo>
<NavButton onClick={handleSearchClick}>
<BiSearch />
찾기
</NavButton>
<NavButton onClick={handleMoviesClick}>
<MdLocalMovies />
영화
</NavButton>
</SidebarContainer>
);
};

export default Sidebar;
32 changes: 32 additions & 0 deletions ben/week2/movie/src/layouts/RootLayout.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.root-container {
display: flex;
flex-direction: column;
min-height: 100vh;
}

.navbar {
position: fixed;
top: 0;
width: 100%;
height: 60px; /* 네비바 높이 조정 가능 */
background-color: #1a1a1a;
z-index: 1000;
}

.main-content {
display: flex;
margin-top: 60px; /* navbar 높이만큼 여백 */
height: calc(100vh - 60px); /* navbar 높이를 제외한 전체 높이 */
}

.sidebar {
width: 240px; /* 사이드바 너비 조정 가능 */
background-color: #222;
flex-shrink: 0;
}

.content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
63 changes: 63 additions & 0 deletions ben/week2/movie/src/layouts/RootLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Outlet } from 'react-router-dom';
import styled from 'styled-components';
import Navbar from '../components/Navbar';
import Sidebar from '../components/Sidebar';

const LayoutContainer = styled.div`
display: flex;
min-height: 100vh;
background-color: #141414;
`;

const SidebarWrapper = styled.aside`
width: 250px;
position: fixed;
top: 0;
left: 0;
height: 100vh;
background-color: #1a1a1a;
z-index: 100;
`;

const ContentContainer = styled.div`
flex: 1;
margin-left: 250px; // Sidebar 너비만큼 여백
min-height: 100vh;
position: relative;
`;

const NavbarWrapper = styled.header`
position: fixed;
top: 0;
right: 0;
width: calc(100% - 250px); // Sidebar 너비만큼 제외
height: 60px;
background-color: rgba(26, 26, 26, 0.9);
z-index: 90;
`;

const MainContent = styled.main`
padding: 80px 20px 20px; // 상단에 Navbar 높이만큼 패딩
width: 100%;
min-height: calc(100vh - 60px); // Navbar 높이만큼 제외
`;

const RootLayout = () => {
return (
<LayoutContainer>
<SidebarWrapper>
<Sidebar />
</SidebarWrapper>
<ContentContainer>
<NavbarWrapper>
<Navbar />
</NavbarWrapper>
<MainContent>
<Outlet />
</MainContent>
</ContentContainer>
</LayoutContainer>
);
};

export default RootLayout;
Loading