Skip to content
Closed
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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
# macOS 시스템 파일 제외
.DS_Store.DS_Store
Binary file modified ozoo/.DS_Store
Binary file not shown.
Binary file renamed ozoo/week2/.DS_Store → ozoo/Week3/.DS_Store
Binary file not shown.
27 changes: 27 additions & 0 deletions ozoo/Week3/MoviePage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# .gitignore
.env

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
54 changes: 54 additions & 0 deletions ozoo/Week3/MoviePage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
})
```
28 changes: 28 additions & 0 deletions ozoo/Week3/MoviePage/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
13 changes: 13 additions & 0 deletions ozoo/Week3/MoviePage/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions ozoo/Week3/MoviePage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.0",
"axios": "^1.8.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.4.1",
"tailwindcss": "^4.1.0"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react-swc": "^3.8.0",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"typescript-eslint": "^8.24.1",
"vite": "^6.2.0"
}
}
1 change: 1 addition & 0 deletions ozoo/Week3/MoviePage/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions ozoo/Week3/MoviePage/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#root {
overflow-x: hidden; /* 가로 스크롤 방지 */
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}

.movie-detail-page {
color: white;
}

.backdrop-section {
position: relative;
background-size: cover;
background-position: center;
padding: 6rem 2rem;
}



.cast-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 1.5rem;
padding: 2rem;
}

.cast-card {
text-align: center;
}

.cast-card img {
width: 100px;
height: 100px;
border-radius: 9999px;
object-fit: cover;
margin: auto;
}
42 changes: 42 additions & 0 deletions ozoo/Week3/MoviePage/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import './App.css'
import HomePage from './pages/HomePage';
import MoviesPage from './pages/MoviePage';
import NotFound from "./pages/NotFound.tsx";
import RootLayout from "./layout/root-layout.tsx";
import MovieDetailPage from './pages/MovieDetailPage';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter([
{
path: '/',
element: <RootLayout/>,
errorElement: <NotFound/>,
children: [
{

element: <HomePage/>,
index: true,
},{
path: 'movies',
element: <MoviesPage />,
},
{
path: 'movies/:category',
element: <MoviesPage/>
},{
path: 'movie/:id',
element: <MovieDetailPage/>
}
],
},

]);



function App() {
return <RouterProvider router={router}/>;

}

export default App
1 change: 1 addition & 0 deletions ozoo/Week3/MoviePage/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.
13 changes: 13 additions & 0 deletions ozoo/Week3/MoviePage/src/components/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const LoadingSpinner = () => {
return (

<div className='animate-spin rounded-full border-6 border-t-transparent size-12
border-[#b2dab1]'
role="status">

<span className="sr-only">로딩 중...</span>
</div>


);
}
37 changes: 37 additions & 0 deletions ozoo/Week3/MoviePage/src/components/MovieCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState } from 'react';
import { Movie } from '../types/movie';
import { useNavigate } from 'react-router-dom';

interface MovieCardProps {
movie: Movie;
}

const MovieCard = ({ movie }: MovieCardProps) => {
const [isHovered, setIsHovered] = useState(false);
const navigate = useNavigate();

return (
<div
onClick={() => navigate(`/movie/${movie.id}`)}
className="relative rounded-xl shadow-lg overflow-hidden cursor-pointer
w-44 transition-transform transform duration-300 hover:scale-105"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<img
src={`https://image.tmdb.org/t/p/w200${movie.poster_path}`}
alt={`${movie.title} 영화의 이미지`}
/>

{isHovered && (
<div className="absolute inset-0 bg-gradient-to-t from-black/50
to-transparent backdrop-blur-md text-white p-4 flex flex-col justify-center">
<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>
);
};

export default MovieCard;
29 changes: 29 additions & 0 deletions ozoo/Week3/MoviePage/src/components/PageButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
type PageButtonsProps = {
page: number;
setPage: React.Dispatch<React.SetStateAction<number>>;
};

const PageButtons = ({ page, setPage }: PageButtonsProps) => {
return (
<div className="flex justify-center my-4 gap-4">
<button
onClick={() => setPage((prev) : number => prev - 1)}
className='px-4 py-2 bg-[#dda5e3] text-white rounded-lg shadow-md hover:bg-[#bfa0d7] transition-colors duration-300 disabled:opacity-50
cursor-pointer disabled:cursor-not-allowed'
disabled={page === 1}
>
{'<'}
</button>
<span className="text-xl">{page}</span>
<button
onClick={() => setPage((prev) : number => prev + 1)}
className='px-4 py-2 bg-[#dda5e3] text-white rounded-lg shadow-md hover:bg-[#bfa0d7] transition-colors duration-300 disabled:opacity-50
cursor-pointer disabled:cursor-not-allowed'
>
{'>'}
</button>
</div>
);
};

export default PageButtons;
Loading
Loading