Skip to content
Merged
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
80 changes: 40 additions & 40 deletions Week4/package.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
{
"name": "week3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@tanstack/react-query": "^5.72.1",
"axios": "^1.8.4",
"clsx": "^2.1.1",
"eslint-config-prettier": "^10.1.1",
"js-cookie": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.55.0",
"react-router-dom": "^7.4.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"postcss": "^8.5.3",
"prettier": "3.5.3",
"tailwindcss": "3.4.1",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
"name": "week3",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.0.1",
"@tanstack/react-query": "^5.72.1",
"axios": "^1.8.4",
"clsx": "^2.1.1",
"eslint-config-prettier": "^10.1.1",
"js-cookie": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hook-form": "^7.55.0",
"react-router-dom": "^7.4.1",
"zod": "^3.24.2"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"autoprefixer": "^10.4.21",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"postcss": "^8.5.3",
"prettier": "3.5.3",
"tailwindcss": "3.4.1",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
2 changes: 1 addition & 1 deletion Week4/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import Layout from './components/Layout';
import Layout from './layout/Layout';
import MoviePage from './pages/Movie/MoviePage';
import Movies from './pages/Movie/components/Movies';
import Detail from './pages/Movie/components/Detail';
Expand Down
24 changes: 7 additions & 17 deletions Week4/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

import { TMovie } from '../../types/movieTypes';

import CardImage from './ui/CardImage';
import ROUTES from '../../constants/routes';
import CardCover from './ui/CardCover';

interface CardTypes {
Expand All @@ -12,27 +13,16 @@ interface CardTypes {
function Card({ movie }: CardTypes) {
const navigate = useNavigate();

const [isHover, setIsHover] = useState<boolean>(false);

const showPreview = (): void => setIsHover(true);
const hidePreview = (): void => setIsHover(false);

const navigateToDetail = (movieId: number): void => {
navigate(`/movies/detail/${movieId}`);
};
const navigateToDetail = () => navigate(ROUTES.MOVIE.DETAIL(movie.id));

return (
<div
className="relative overflow-hidden transition-all duration-300 cursor-pointer rounded-2xl "
onMouseEnter={showPreview}
onMouseLeave={hidePreview}
onClick={() => navigateToDetail(movie.id)}
className="relative overflow-hidden transition-all duration-300 cursor-pointer rounded-2xl group"
onClick={navigateToDetail}
>
<CardImage movie={movie} isHover={isHover} />
<CardImage movie={movie} />

{isHover && (
<CardCover title={movie.title} overview={movie.overview} />
)}
<CardCover movie={movie} />
</div>
);
}
Expand Down
15 changes: 9 additions & 6 deletions Week4/src/components/Card/ui/CardCover.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { TMovie } from '../../../types/movieTypes';

interface CardCoverProps {
title: string;
overview: string | null;
movie: TMovie;
}

function CardCover({ title, overview }: CardCoverProps) {
function CardCover({ movie }: CardCoverProps) {
return (
<div className="absolute inset-0 flex flex-col justify-center px-4 py-4 text-center transition-opacity duration-300 bg-black bg-opacity-60">
<h5 className="mb-4 text-xl text-white">{title}</h5>
<p className="overflow-hidden text-sm text-white">{overview}</p>
<div className="absolute inset-0 flex flex-col justify-center px-4 py-4 text-center transition-opacity duration-300 bg-black opacity-0 pointer-events-none bg-opacity-60 group-hover:opacity-100">
<h5 className="mb-4 text-xl text-white">{movie.title}</h5>
<p className="overflow-hidden text-sm text-white">
{movie.overview}
</p>
</div>
);
}
Expand Down
9 changes: 2 additions & 7 deletions Week4/src/components/Card/ui/CardImage.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import clsx from 'clsx';
import { TMovie } from '../../../types/movieTypes';

interface CardImageProps {
movie: TMovie;
isHover: boolean;
}

function CardImage({ movie, isHover }: CardImageProps) {
function CardImage({ movie }: CardImageProps) {
return (
<img
src={`https://image.tmdb.org/t/p/w300${movie.poster_path}`}
alt={movie.title}
className={clsx(
'object-cover transition duration-300',
isHover && 'blur-sm scale-105',
)}
className="object-cover transition duration-300 pointer-events-none group-hover:blur-sm group-hover:scale-105"
/>
);
}
Expand Down
5 changes: 3 additions & 2 deletions Week4/src/components/Navbar/ui/AuthLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import ROUTES from '../../../constants/routes';
import AuthLink from './AuthLink';

function AuthLinks() {
return (
<div className="flex gap-4">
<AuthLink to="/login">Log in</AuthLink>
<AuthLink to="/signup">Sign Up</AuthLink>
<AuthLink to={ROUTES.LOGIN}>Log in</AuthLink>
<AuthLink to={ROUTES.SIGNUP}>Sign Up</AuthLink>
</div>
);
}
Expand Down
7 changes: 3 additions & 4 deletions Week4/src/components/Navbar/ui/MovieLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ function MovieLink({ children, to }: MovieLinkProps) {
to={to}
className={({ isActive }) =>
clsx(
'hover:text-indigo-600',
isActive
? 'font-bold text-indigo-700 underline underline-offset-4'
: 'text-gray-700',
'hover:text-indigo-600 text-gray-700',
isActive &&
'font-bold text-indigo-700 underline underline-offset-4',
)
}
>
Expand Down
3 changes: 2 additions & 1 deletion Week4/src/components/Navbar/ui/MovieLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import ROUTES from '../../../constants/routes';
import { navItems } from '../constants/NavItems';
import MovieLink from './MovieLink';

function MovieLinks() {
return (
<div className="flex gap-4">
{navItems.map(({ label, path }) => (
<MovieLink key={path} to={`/movies/${path}`}>
<MovieLink key={path} to={ROUTES.MOVIE.CATEGORY(path)}>
{label}
</MovieLink>
))}
Expand Down
3 changes: 2 additions & 1 deletion Week4/src/components/Navbar/ui/NavLogo.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Link } from 'react-router-dom';
import ROUTES from '../../../constants/routes';

function NavLogo() {
return (
<Link
to="/"
to={ROUTES.HOME}
className="mr-8 text-3xl italic font-extrabold text-gray-900 transition hover:text-indigo-600"
>
Movie Archive
Expand Down
3 changes: 1 addition & 2 deletions Week4/src/components/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Button from './ui/Button';
import Page from './ui/Page';

interface Tpagination {
page: number;
Expand All @@ -17,7 +16,7 @@ function Pagination({ page, setPage, maxPage }: Tpagination) {
</Button>

<Page page={page} maxPage={maxPage} />
<span className="text-xl font-semibold text-gray-800">{`${page} / ${maxPage}`}</span>

<Button onClick={nextPage} disabled={page === maxPage}>
Expand Down
4 changes: 2 additions & 2 deletions Week4/src/components/Pagination/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ function Button({ onClick, disabled, children }: ButtonProps) {
onClick={onClick}
disabled={disabled}
className={clsx(
'w-8 h-8 flex items-center justify-center text-base font-semibold rounded-full transition-all',
'bg-slate-500 hover:bg-slate-700 text-white shadow hover:shadow-lg',
'w-8 h-8 flex items-center justify-center text-base font-semibold rounded-full transition-all text-white shadow bg-slate-500',
' hover:bg-slate-700 hover:shadow-lg',
'disabled:bg-slate-200 disabled:text-slate-400 disabled:cursor-not-allowed',
)}
>
Expand Down
14 changes: 14 additions & 0 deletions Week4/src/constants/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const ROUTES = {
HOME: '/',
LOGIN: '/login',
SIGNUP: '/signup',
MYPAGE: '/my',

MOVIE: {
ROOT: '/movies',
CATEGORY: (category: string) => `/movies/${category}`,
DETAIL: (id: number | string) => `/movies/detail/${id}`,
},
};

export default ROUTES;
11 changes: 7 additions & 4 deletions Week4/src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ function useFetch<T>(url: string | undefined, page?: number) {
const fetcher = async (): Promise<T> => {
if (!url) throw new Error('URL is undefined');

const response = await axios.get(`${url}${`?page=${page}`}`, {
headers: {
Authorization: `Bearer ${import.meta.env.VITE_TMDB_KEY}`,
const response = await axios.get(
`${url}${page ? `?page=${page}` : ``}`,
{
headers: {
Authorization: `Bearer ${import.meta.env.VITE_TMDB_KEY}`,
},
},
});
);

return response.data;
};
Expand Down
26 changes: 26 additions & 0 deletions Week4/src/hooks/useHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useRef, useState, useCallback } from 'react';

function useHover<T extends HTMLElement = HTMLElement>() {
const [isHover, setIsHover] = useState<boolean>(false);
const ref = useRef<T | null>(null);

const handleMouseEnter = useCallback(() => setIsHover(true), []);
const handleMouseLeave = useCallback(() => setIsHover(false), []);

useEffect(() => {
const element = ref.current;
if (!element) return;

element.addEventListener('mouseenter', handleMouseEnter);
element.addEventListener('mouseleave', handleMouseLeave);

return () => {
element.removeEventListener('mouseenter', handleMouseEnter);
element.removeEventListener('mouseleave', handleMouseLeave);
};
}, [handleMouseEnter, handleMouseLeave]);

return [ref, isHover] as const; // 튜플 [RefObject<T|null>, boolean]
}

export default useHover;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Navbar from './Navbar/Navbar';
import Navbar from '../components/Navbar/Navbar';
import { Outlet } from 'react-router-dom';

function Layout() {
Expand Down
38 changes: 14 additions & 24 deletions Week4/src/pages/Movie/components/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,37 @@ function Detail() {
const detailUrl = `${import.meta.env.VITE_TMDB_URL_DETAIL}/${id}`;
const creditsUrl = detailUrl + '/credits';

const getBackdropUrl = (path: string | null) =>
path ? `https://image.tmdb.org/t/p/original${path}` : '/backdrop.png';

const getProfileUrl = (path: string | null) =>
path ? `https://image.tmdb.org/t/p/original${path}` : '/profile.png';

const {
data: movieData,
isPending: isPending1,
isError: isError1,
} = useFetch<TMovieDetail>(detailUrl);

const {
data: creditsData,
isPending: isPending2,
isError: isError2,
} = useFetch<TMovieCredits>(creditsUrl);

if (
isPending1 ||
isPending2 ||
isError1 ||
isError2 ||
!movieData ||
!creditsData
) {
console.log({
isPending1,
isPending2,
isError1,
isError2,
movieData,
creditsData,
});
const isLoading = isPending1 || isPending2;
const isError = isError1 || isError2;
const isEmpty = !movieData || !creditsData;

if (isLoading || isError || isEmpty) {
return (
<div className="flex items-center justify-center h-dvh">
{(isPending1 || isPending2) && <LoadingSpinner />}
{(isError1 || isError2) && <ErrorMessage />}
{isLoading && <LoadingSpinner />}
{isError && <ErrorMessage />}
</div>
);
}

const getBackdropUrl = (path: string | null) =>
path ? `https://image.tmdb.org/t/p/original${path}` : '/backdrop.png';

const getProfileUrl = (path: string | null) =>
path ? `https://image.tmdb.org/t/p/original${path}` : '/profile.png';

return (
<main className="bg-[#343434]">
<div className="flex items-start gap-6 p-6">
Expand Down
Loading