Skip to content
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
1,426 changes: 1,426 additions & 0 deletions keyword/chapter04/keyword_04.md

Large diffs are not rendered by default.

Empty file.
30 changes: 30 additions & 0 deletions mission/chapter04/mission_1/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import './App.css';
import HomePage from './pages/HomePage';
import MoviePage from './pages/MoviePage';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import NotFoundPage from './pages/NotFoundPage';
import MovieDetailPage from './pages/MovieDetailPage';

const router = createBrowserRouter([
{
path: '/',
element: <HomePage />,
errorElement: <NotFoundPage />,
children: [
//영화 상세페이지로
{
path: 'movies/detail/:movieId',
element: <MovieDetailPage />,
},
{
path: 'movies/:category',
element: <MoviePage />,
},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}

export default App;
31 changes: 31 additions & 0 deletions mission/chapter04/mission_1/src/api/apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import axios from 'axios';
import type { MovieResponse, MovieDetails, Credits } from '../types/movie';

const axiosInstance = axios.create({
baseURL: 'https://api.themoviedb.org/3',
headers: {
Authorization: `Bearer ${import.meta.env.VITE_TMDB_KEY}`,
},
});

export const getPopularMovies = async (
category: string = 'popular',
page: number = 1,
): Promise<MovieResponse> => {
const { data } = await axiosInstance.get<MovieResponse>(
`/movie/${category}?language=en-US&page=${page}`,
);
return data;
};

export const getMovieDetails = async (movieId: string) => {
const { data } = await axiosInstance.get<MovieDetails>(`/movie/${movieId}`);
return data;
};

export const getMovieCredits = async (movieId: string) => {
const { data } = await axiosInstance.get<Credits>(
`/movie/${movieId}/credits`,
);
return data;
};
Binary file added mission/chapter04/mission_1/src/assets/hero.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mission/chapter04/mission_1/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mission/chapter04/mission_1/src/assets/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const LoadingSpinner = () => {
return <div className='size-12 animate-spin rounded-full border-6
border-t-transparent border-[#b2dab1]' role='staus'>
<span className='sr-only'>로딩 중...</span>
</div>
}
43 changes: 43 additions & 0 deletions mission/chapter04/mission_1/src/components/MovieCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Movie } from '../types/movie';
import { useNavigate } from 'react-router-dom';

//카드로 영화 보여주고 싶어서 만드는 컴포넌트

//movie card의 type 정의
interface MovieCardProps {
movie: Movie;
}

export default function MovieCard({ movie }: MovieCardProps) {
const navigate = useNavigate();

//<img /> 카드의 이미지 불러오기 + alt로 이미지 안 나올 때 에러처리
//isHoverd로 마우스 갖다댈 때 처리
return (
<div
onClick={() => navigate(`/movies/detail/${movie.id}`)}
className="group relative rounded-xl shadow-lg overflow-hidden cursor-pointer w-44
transition-transform duration-500 hover:scale-105"
>
<img
src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
alt={`${movie.title} 영화의 이미지`}
className=""
/>

<div
className="absolute inset-0 bg-gradient-to-t from-black/50
to-transparent backdrop-blur-md flex flex-col justify-center
items-center text-white p-4
opacity-0 group-hover:opacity-100 transition-opacity duration-300"
>
<h2 className="text-lg font-bold text-center leading-snug">
{movie.title}
</h2>
<p className="text-sm text-gray-300 leading-relaxed mt-2 line-clamp-5">
{movie.overview}
</p>
</div>
</div>
);
}
27 changes: 27 additions & 0 deletions mission/chapter04/mission_1/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { NavLink } from 'react-router-dom';

const LINKS = [
{ to: '/', label: '홈' },
{ to: '/movies/popular', label: '인기 영화' },
{ to: '/movies/now_playing', label: '상영 중' },
{ to: '/movies/top_rated', label: '평점 높은' },
{ to: '/movies/upcoming', label: '개봉 예정' },
];

export const Navbar = () => {
return (
<div className="flex gap-3 p-4">
{LINKS.map(({ to, label }) => (
<NavLink
key={to}
to={to}
className={({isActive}) => {
return isActive ? 'text-[navy] font-bold' : 'text-gray-500';
}}
>
{label}
</NavLink>
))}
</div>
);
};
9 changes: 9 additions & 0 deletions mission/chapter04/mission_1/src/hooks/useError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useState } from 'react';

function useError(initialValue: boolean = false) {
const [isError, setIsError] = useState(initialValue);

return [isError, setIsError] as const;
}

export default useError;
9 changes: 9 additions & 0 deletions mission/chapter04/mission_1/src/hooks/usePending.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useState } from 'react';

function usePending(initialValue: boolean = false) {
const [isPending, setIsPending] = useState(initialValue);

return [isPending, setIsPending] as const;
}

export default usePending;
1 change: 1 addition & 0 deletions mission/chapter04/mission_1/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import 'tailwindcss';
10 changes: 10 additions & 0 deletions mission/chapter04/mission_1/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'

createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
13 changes: 13 additions & 0 deletions mission/chapter04/mission_1/src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Outlet } from 'react-router-dom';
import { Navbar } from '../components/Navbar';

const HomePage = () => {
return (
<>
<Navbar />
<Outlet />
</>
);
};

export default HomePage;
Loading