Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8c68696
initial_settings
hoospacekor Apr 22, 2023
1af2ada
README_setup
hoospacekor Apr 22, 2023
3ef3ed2
favicon_upload&routes_setup
hoospacekor Apr 22, 2023
277e482
Header_initial_draft
hoospacekor Apr 23, 2023
3493be3
getMovieRequest
hoospacekor Apr 24, 2023
f6d085b
components_seperation
hoospacekor Apr 24, 2023
2f15fab
SearchBox_resolved
hoospacekor Apr 24, 2023
db2980a
Add&Remove_Handlers_Updated
hoospacekor Apr 24, 2023
d0e7b72
addLocalStorage
hoospacekor Apr 24, 2023
549658e
InitialLoading
hoospacekor Apr 24, 2023
6438293
LocalStorage_completed
hoospacekor Apr 24, 2023
ceccc77
list_decoration
hoospacekor Apr 25, 2023
fd40727
scrollbar_hidden
hoospacekor Apr 25, 2023
4e59bce
search_onKeyDown_changed
hoospacekor Apr 25, 2023
bf3ca14
home_CSS_done
hoospacekor Apr 25, 2023
5847edc
SearchBar
hoospacekor Apr 25, 2023
96c0b94
About_onClickHandler
hoospacekor Apr 25, 2023
a14c48a
Header_component_seperation
hoospacekor Apr 26, 2023
648d13e
NotFound_Page_created
hoospacekor Apr 26, 2023
c80bc31
Profile_Page
hoospacekor Apr 26, 2023
24bf600
MovieInfo_layout
hoospacekor Apr 26, 2023
57a794c
MovieInfo_transfer_success
hoospacekor Apr 26, 2023
1ce81ec
movieinfo
hoospacekor Apr 26, 2023
626428d
Info_data_filled
hoospacekor Apr 26, 2023
403863f
Mainpage_MovieHeading_Approach_solved
hoospacekor Apr 27, 2023
1c9c986
Description_styled
hoospacekor Apr 27, 2023
9a20796
img_resized
hoospacekor Apr 28, 2023
911f9f6
AutoScroll_applied
hoospacekor Apr 28, 2023
607027a
map_error_resolved
hoospacekor Apr 29, 2023
1be78b0
Infinite_Scroll_initial_test
hoospacekor Apr 29, 2023
d0deccb
Comments
hoospacekor Apr 29, 2023
413a464
InfiniteScroll
hoospacekor May 1, 2023
4826fcd
README_Final_Version
hoospacekor May 2, 2023
7025bb7
VercelConfig
hoospacekor May 2, 2023
d4d5bf7
netlify
hoospacekor May 2, 2023
cebfb81
path_correction
hoospacekor May 2, 2023
1608176
empty_array_test
hoospacekor May 2, 2023
d0dd371
empty_array_test2
hoospacekor May 2, 2023
eccf5d0
empty_array_test3
hoospacekor May 2, 2023
497d2a8
deployment_error_resolved
hoospacekor May 2, 2023
6721461
loadList_error_test1
hoospacekor May 2, 2023
5bd4ed8
overflowAutoCorrection
hoospacekor May 4, 2023
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
16 changes: 16 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh', 'prettier'],
rules: {
'react-refresh/only-export-components': 'warn',
},
};
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
9 changes: 9 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"semi": false,
"singleQuote": true,
"endOfLine": "lf",
"singleAttributePerLine": true,
"bracketSameLine": true,
"trailingComma": "none",
"arrowParens": "avoid"
}
286 changes: 84 additions & 202 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,219 +1,101 @@
# 🎬 영화 검색
# 🎬 OMDb API 활용 영화검색 사이트 'CineMap'

주어진 API를 활용해 '[완성 예시](https://stupefied-hodgkin-d9d350.netlify.app/)' 처럼 자유롭게 영화 검색 기능을 구현해보세요!
과제 수행 및 리뷰 기간은 별도 공지를 참고하세요!
## 배포주소

## 과제 수행 및 제출 방법
> <a href="https://kdt5-ahnjoonghoo--snazzy-custard-444e32.netlify.app/">CINEMAP</a>

```
KDT기수번호_이름 | E.g, KDT0_ParkYoungWoong
```
---

1. 현재 저장소를 로컬에 클론(Clone)합니다.
1. 자신의 본명으로 브랜치를 생성합니다.(구분 가능하도록 본명을 꼭 파스칼케이스로 표시하세요, `git branch KDT0_ParkYoungWoong`)
1. 자신의 본명 브랜치에서 과제를 수행합니다.
1. 과제 수행이 완료되면, 자신의 본명 브랜치를 원격 저장소에 푸시(Push)합니다.(`main` 브랜치에 푸시하지 않도록 꼭 주의하세요, `git push origin KDT0_ParkYoungWoong`)
1. 저장소에서 `main` 브랜치를 대상으로 Pull Request 생성하면, 과제 제출이 완료됩니다!(E.g, `main` <== `KDT0_ParkYoungWoong`)
## 화면 구성 🪟

- `main` 혹은 다른 사람의 브랜치로 절대 병합하지 않도록 주의하세요!
- Pull Request에서 보이는 설명을 다른 사람들이 이해하기 쉽도록 꼼꼼하게 작성하세요!
- Pull Request에서 과제 제출 후 절대 병합(Merge)하지 않도록 주의하세요!
- 과제 수행 및 제출 과정에서 문제가 발생한 경우, 바로 담당 멘토나 강사에서 얘기하세요!
| 메인 페이지 |
| :-----------------------------------------------------------------------------------------------------------------------------: |
| <img width="600" src="https://user-images.githubusercontent.com/115582699/235587478-119bd187-0913-460d-8602-b4c54faada3c.png"/> |
| **상세정보 페이지** |
| <img width="600" src="https://user-images.githubusercontent.com/115582699/235587566-bff7b6b0-6aae-4bef-bb01-386c310297e4.png"/> |
| **소개 페이지** |
| <img width="600" src="https://user-images.githubusercontent.com/115582699/235587572-a9b6f4c3-a249-4035-aae2-9de61038cb73.png"/> |

## 요구사항
---

## 기술 스택

### Config

![Vite](https://img.shields.io/badge/VITE-646CFF?style=flat-square&logo=vite&logoColor=white) ![Npm](https://img.shields.io/badge/NPM-CB3837?style=flat-square&logo=npm&logoColor=white)

### Development

![HTML5](https://img.shields.io/badge/HTML5-E34F26?style=flat-square&logo=html5&logoColor=white) ![JAVASCRIPT](https://img.shields.io/badge/JAVASCRIPT-F7DF1E?style=flat-square&logo=javascript&logoColor=white) ![React](https://img.shields.io/badge/REACT-61DAFB?style=flat-square&logo=react&logoColor=white) ![MUI](https://img.shields.io/badge/MUI-007FFF?style=flat-square&logo=mui&logoColor=white)

### Deployment

<img src="https://img.shields.io/badge/NETLIFY-00C7B7?style=flat-square&logo=netlify&logoColor=white">

필수 요구사항은 꼭 달성해야 하는 목표로, 수정/삭제는 불가하고 추가는 가능합니다.
선택 요구사항은 단순 예시로, 자유롭게 추가/수정/삭제해서 구현해보세요.
각 요구사항은 달성 후 마크다운에서 `- [x]`로 표시하세요.
---

## 요구사항

### ❗ 필수

- [ ] 영화 제목으로 검색이 가능해야 합니다!
- [ ] 검색된 결과의 영화 목록이 출력돼야 합니다!
- [ ] 단일 영화의 상세정보(제목, 개봉연도, 평점, 장르, 감독, 배우, 줄거리, 포스터 등)를 볼 수 있어야 합니다!
- [ ] 실제 서비스로 배포하고 접근 가능한 링크를 추가해야 합니다.
- [x] 영화 제목으로 검색이 가능해야 합니다!
- [x] 검색된 결과의 영화 목록이 출력돼야 합니다!
- [x] 단일 영화의 상세정보(제목, 개봉연도, 평점, 장르, 감독, 배우, 줄거리, 포스터 등)를 볼 수 있어야 합니다!
- [x] 실제 서비스로 배포하고 접근 가능한 링크를 추가해야 합니다.

### ❔ 선택

- [ ] 한 번의 검색으로 영화 목록이 20개 이상 검색되도록 만들어보세요.
- [ ] 영화 개봉연도로 검색할 수 있도록 만들어보세요.
- [ ] 영화 목록을 검색하는 동안 로딩 애니메이션이 보이도록 만들어보세요.
- [ ] 무한 스크롤 기능을 추가해서 추가 영화 목록을 볼 수 있도록 만들어보세요.
- [ ] 영화 포스터가 없을 경우 대체 이미지를 출력하도록 만들어보세요.
- [x] 무한 스크롤 기능을 추가해서 추가 영화 목록을 볼 수 있도록 만들어보세요.
- [x] 영화 포스터가 없을 경우 대체 이미지를 출력하도록 만들어보세요.
- [ ] 영화 상세정보가 출력되기 전에 로딩 애니메이션이 보이도록 만들어보세요.
- [ ] 영화 상세정보 포스터를 고해상도로 출력해보세요. (실시간 이미지 리사이징)
- [ ] 차별화가 가능하도록 프로젝트를 최대한 예쁘게 만들어보세요.
- [ ] 영화와 관련된 기타 기능도 고려해보세요.

## API 기본 사용법

```curl
curl https://omdbapi.com/?apikey=7035c60c
\ -X 'GET'
```

## 영화 목록 검색

영화 목록은 한 번에 최대 10개까지 검색할 수 있습니다.

파라미터 | 설명 | 기본값
---|----------------------|---
`s` | 검색할 영화 제목(필수!) | -
`y` | 검색할 개봉연도, 빈 값은 전체 검색 | -
`page` | 검색할 페이지 번호 | `1`

요청 코드 예시:

```js
async function getMovies(title, year = '', page = 1) {
const s = `&s=${title}`
const y = `&y=${year}`
const p = `&page=${page}`
try {
const res = await fetch(`https://omdbapi.com/?apikey=7035c60c${s}${y}${p}`)
const json = await res.json()
if (json.Response === 'True') {
const { Search: movies, totalResults } = json
return {
movies,
totalResults
}
}
return json.Error
} catch (error) {
console.log(error)
}
}
```

응답 데이터 타입 및 예시:

```ts
interface ResponseValue {
Search: Movie[] // 검색된 영화 목록, 최대 10개
totalResults: string // 검색된 영화 개수
Response: 'True' | 'False' // 요청 성공 여부
}
interface Movie {
Title: string // 영화 제목
Year: string // 영화 개봉연도
imdbID: string // 영화 고유 ID
Type: string // 영화 타입
Poster: string // 영화 포스터 이미지 URL
}
```

```json
{
"Search": [
{
"Title": "Frozen",
"Year": "2013",
"imdbID": "tt2294629",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BMTQ1MjQwMTE5OF5BMl5BanBnXkFtZTgwNjk3MTcyMDE@._V1_SX300.jpg"
},
{
"Title": "Frozen II",
"Year": "2019",
"imdbID": "tt4520988",
"Type": "movie",
"Poster": "https://m.media-amazon.com/images/M/MV5BMjA0YjYyZGMtN2U0Ni00YmY4LWJkZTItYTMyMjY3NGYyMTJkXkEyXkFqcGdeQXVyNDg4NjY5OTQ@._V1_SX300.jpg"
}
],
"totalResults": "338",
"Response": "True"
}
```

## 영화 상제정보 검색

단일 영화의 상제정보를 검색합니다.

파라미터 | 설명 | 기본값
---|---|---
`i` | 검색할 영화 ID(필수!) |
`plot` | 줄거리 길이 | `short`

요청 코드 예시:

```js
async function getMovie(id) {
const res = await fetch(`https://omdbapi.com/?apikey=7035c60c&i=${id}&plot=full`)
const json = await res.json()
if (json.Response === 'True') {
return json
}
return json.Error
}
```

응답 데이터 타입 및 예시:

```ts
interface ResponseValue {
Title: string // 영화 제목
Year: string // 영화 개봉연도
Rated: string // 영화 등급
Released: string // 영화 개봉일
Runtime: string // 영화 상영시간
Genre: string // 영화 장르
Director: string // 영화 감독
Writer: string // 영화 작가
Actors: string // 영화 출연진
Plot: string // 영화 줄거리
Language: string // 영화 언어
Country: string // 영화 제작 국가
Awards: string // 영화 수상 내역
Poster: string // 영화 포스터 이미지 URL
Ratings: Rating[] // 영화 평점 정보
Metascore: string // 영화 메타스코어
imdbRating: string // 영화 IMDB 평점
imdbVotes: string // 영화 IMDB 투표 수
imdbID: string // 영화 고유 ID
Type: string // 영화 타입
DVD: string // 영화 DVD 출시일
BoxOffice: string // 영화 박스오피스
Production: string // 영화 제작사
Website: string // 영화 공식 웹사이트
Response: string // 요청 성공 여부
}
interface Rating { // 영화 평점 정보
Source: string // 평점 제공 사이트
Value: string // 평점
}
```

```json
{
"Title": "Frozen",
"Year": "2013",
"Rated": "PG",
"Released": "27 Nov 2013",
"Runtime": "102 min",
"Genre": "Animation, Adventure, Comedy",
"Director": "Chris Buck, Jennifer Lee",
"Writer": "Jennifer Lee, Hans Christian Andersen, Chris Buck",
"Actors": "Kristen Bell, Idina Menzel, Jonathan Groff",
"Plot": "When the newly crowned Queen Elsa accidentally uses her power to turn things into ice to curse her home in infinite winter, her sister Anna teams up with a mountain man, his playful reindeer, and a snowman to change the weather co...",
"Language": "English, Norwegian",
"Country": "United States",
"Awards": "Won 2 Oscars. 82 wins & 60 nominations total",
"Poster": "https://m.media-amazon.com/images/M/MV5BMTQ1MjQwMTE5OF5BMl5BanBnXkFtZTgwNjk3MTcyMDE@._V1_SX300.jpg",
"Ratings": [
{ "Source": "Internet Movie Database", "Value": "7.4/10" },
{ "Source": "Rotten Tomatoes", "Value": "90%" },
{ "Source": "Metacritic", "Value": "75/100" }
],
"Metascore": "75",
"imdbRating": "7.4",
"imdbVotes": "620,489",
"imdbID": "tt2294629",
"Type": "movie",
"DVD": "18 Mar 2014",
"BoxOffice": "$400,953,009",
"Production": "N/A",
"Website": "N/A",
"Response": "True"
}
```
- [x] 영화 상세정보 포스터를 고해상도로 출력해보세요.(실시간 이미지 리사이징)
- [x] 차별화가 가능하도록 프로젝트를 최대한 예쁘게 만들어보세요.
- [x] 영화와 관련된 기타 기능도 고려해보세요.

---

## 추가기능

### Watch Later

- 영화 검색 후 포스터 우측 하단에서 아이콘을 클릭할 시 LocalStorage를 활용해 WatchLater에 선택한 영화를 저장 후 하단 목록에 출력될 수 있도록 했습니다.

### 좌/우 커서 이동시 자동 스크롤

1. 가로스크롤을 사용하도록 화면을 구성했기 때문에 노트북 트랙패드/터치패드/태블릿 등을 사용할 시 스크롤에 어려움이 없지만 **일반 데스크탑 환경에서 마우스를 사용할 때 Shift + 마우스 휠을 통해 조작해야 사용가능**하다는 점을 확인했습니다.
2. 따라서 커서의 이동만으로 좌/우 스크롤을 구현하는 것이 사용자 경험 측면에서는 필수적인 기능이라고 판단했고 기존의 세로스크롤 라이브러리를 수정해 가로스크롤에 맞게 적용했습니다.
3. 또한 아이콘을 우측 **하단**에 배치시킴으로 인해 **스크롤바와 위치가 겹치는 문제점이 발생**했고 스크롤바를 CSS를 통해 보이지 않도록 설정했습니다. 따라서 스크롤바가 없을 때 추가적인 목록 확인을 위해 목록 우측으로 커서를 위치시키는 것이 사용자로부터 예측 가능한 다음 행동 중 하나임을 고려해 자동스크롤을 적용했습니다.

## 프로젝트 회고

### 배운점(Insight)

- 1차 과제에서 **README**에 대한 피드백을 받았고 이번 과제를 위해 다양한 예시들을 찾아보면서 문서의 중요성 및 작성방법을 배웠습니다.

- 개인적으로 구글의 모든 제품에 관심이 많아서 1차 과제에서도 Material Design 웹사이트 레이아웃을 클론코딩했습니다. 이번에는 더 나아가 Material Design이 적용된 MUI를 사용해볼 수 있어 몰입해서 프로젝트를 진행할 수 있었고 이전 멘토링 답변에서 들었던 **생산성과 자동화**를 React + MUI를 통해 느낄 수 있었습니다.

- MUI사용으로 인해 더 효율적인 작업이 가능할 걸로 기대했지만 사용법에 익숙해지기까지 많은 시간이 걸린다는 걸 간과해 간단한 스타일 적용에도 어려움을 겪었습니다. 아무리 좋은 기술이더라도 초기에는 **학습시간에 대한 기회비용을 고려**해야한다는 점을 배웠습니다.

- (검색 => 포스터 클릭 => 영화 상세정보 조회)의 과정에서 데이터 전달시 컴포넌트 구성이 (조상 => 부모 => 자식) 구조였다면 props 전달이 용이했겠지만 (부모 => 자식 => 페이지 변경 후 데이터 전달)의 과정이 되었고 결과적으로 **useNavigate에서 state를 전달할 수 있는 방법**이 있다는 걸 배웠습니다.

### 문제점(Challenge)

- React로 만들어진 해당 프로젝트 내에 사용된 기술들의 작동원리를 **JavaScript**로 모두 설명할 수 있을지 자문해보면 아직 못할 것 같습니다. 또한 과제 진행 중 마주한 오류들에서도 JS기본의 중요성을 느꼈고 꾸준한 자바스크립트 학습의 필요성을 느꼈습니다.

- MUI사용으로 별도의 CSS파일 관리 없이 스타일 작업이 가능했지만 **컴포넌트에 직접 작성된 스타일 속성**들로 인해 **가독성**을 해친다는 느낌을 받았습니다.

- 자동스크롤의 모션이 부드럽지 않지만 라이브러리 사용으로 인해 어떤 방식으로 수정을 해야할지 갈피를 잡지 못해 작동 확인에만 그친 후 추가적인 수정을 하지 못했습니다.

### 궁금한 점(Questions)

- MUI사용시 Layout설정시 **App Bar, Stack, Box**등 여러 방법의 차이점이 궁금합니다. HTML에서는 1. div + class를 사용시/2. Semantic tags를 사용시 의미적으로 명확한 차이가 있지만 이미 만들어진 컴포넌트를 사용시 어떤 차이점에 주의해야할지 궁금합니다.

- 처음엔 **Home.jsx에 렌더링**을 바로 시도했지만 가장 먼저 확인하게 되는 페이지에서 긴 코드를 마주하게 되어 App.jsx로 컴포넌트 분리 후 Home은 App을 import해 홈페이지라는 명시를 위한 용도로만 사용했습니다. 라우터 관리 시에 더 보편적이고 좋은 방식이 어떤건지 궁금합니다.

- 1.App.jsx(Home)에서 사용한 **AppHeader.jsx**로 별개의 Component로 분리했지만 App.jsx 내에서 SearchBox.jsx를 AppHeader내부로 적용시키지 못해 **컴포넌트 사용이 아닌 AppHeader.jsx내부의 코드를 작성해 감싸 Header를 구성**했습니다.<br>
2.1.<u>SearchBox 컴포넌트를 AppHeader 내부에 하나의 컴포넌트로 병합시키는 방법</u>도 고려했지만 Movie/About등 타 페이지의 헤더를 검색창 없이 구현하고 싶었습니다. <br>
2.2 <u>SearchBox가 검색기능로직에 관련한 데이터를 사용하고 있었기 때문에도 분리의 어려움</u>이 있었습니다.) <br>
위와 같은 문제에서 더 좋은 방식이 있는지 궁금합니다.
22 changes: 22 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link
rel="icon"
href="/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/reset.min.css" />
<title>CineMap</title>
</head>
<body>
<div id="root"></div>
<script
type="module"
src="/src/main.jsx"></script>
</body>
</html>
4 changes: 4 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
Loading