Skip to content

Create Week10 Mission1-2 - #86

Open
huheyun wants to merge 1 commit into
mainfrom
gureum/Week10
Open

Create Week10 Mission1-2#86
huheyun wants to merge 1 commit into
mainfrom
gureum/Week10

Conversation

@huheyun

@huheyun huheyun commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

📝 미션 번호

10주차 Misson 1-2

📋 구현 사항

  • 영화 검색기능
  • 영화 정보 상세 모달
  • 최적화
  • 배포

📎 스크린샷

✅ 체크리스트

  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

🤔 질문 사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 영화 검색 기능 추가 (언어 및 성인물 포함 여부 필터링 지원)
    • 인기/개봉 예정/평점 높은 영화 카테고리별 목록 조회
    • 영화 상세 정보 모달 및 전용 페이지 제공
    • 주요 출연진, 감독, 상세 메타데이터 표시
    • IMDb 외부 링크 연동
    • 페이지네이션 지원
  • Documentation

    • 프로젝트 실행 방법 및 환경 설정 가이드 추가
  • Chores

    • ESLint, TypeScript, Vite 개발 환경 설정
    • 빌드 및 배포 스크립트 구성
    • Vercel SPA 라우팅 설정

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

TMDB API를 사용하는 영화 검색 React SPA를 Week10/gureum/mission1 경로에 신규로 추가한다. Vite + TypeScript + TailwindCSS 기반으로 프로젝트를 구성하고, 영화 목록/검색/상세 기능과 React Router 기반 라우팅, 메모이제이션 최적화, Vercel SPA 배포 설정을 포함한다.

Changes

영화 검색 앱 전체 구현

Layer / File(s) Summary
TMDB 타입 계약 및 API 클라이언트
src/types/movie.ts, src/api/tmdb.ts
MovieListType, MovieLanguage, Movie, MovieDetail, Credits 등 TMDB 응답 타입을 정의하고, Bearer 인증 Axios 인스턴스와 fetchMovies, searchMovies, fetchMovieDetail, fetchMovieCredits 4개의 API 래퍼 함수를 추가한다.
범용 useFetch 훅 및 도메인 훅
src/hooks/useFetch.ts, src/hooks/useMovies.ts, src/hooks/useMovieDetail.ts, src/hooks/useMovieSearch.ts
마운트 해제 안전성(isMountedRef)과 refetch 기능을 갖춘 useFetch를 구현하고, 이를 기반으로 목록 페이지네이션(useMovies), 상세+크레딧 병렬 조회(useMovieDetail), 검색 draft/submitted 분리(useMovieSearch) 도메인 훅을 구성한다.
공통 UI 컴포넌트
src/components/common/LoadingSpinner.tsx, src/components/common/ErrorState.tsx
크기 옵션을 갖춘 로딩 스피너와 선택적 재시도 버튼을 포함한 에러 상태 컴포넌트를 추가한다.
영화 목록 UI
src/components/movies/MovieGrid.tsx, src/components/movies/Pagination.tsx, src/components/movies/MovieSearchForm.tsx, src/components/movies/MovieModal.tsx
memo 기반 영화 카드 그리드, 이전/다음 페이지네이션, 언어·성인포함 옵션을 갖춘 검색 폼, 그리고 useMovieDetail로 상세 데이터를 조회하는 모달 컴포넌트를 구현한다.
영화 상세 UI
src/components/movie-detail/MovieHero.tsx, src/components/movie-detail/CastGrid.tsx, src/components/movie-detail/MovieMetaCards.tsx
배경/포스터/평점/장르/IMDb 링크를 포함하는 히어로 배너, 출연진 그리드, 기본정보·박스오피스 메타 카드를 추가한다. MovieHero는 모달(닫기 버튼)과 상세 페이지(뒤로가기 링크) 두 컨텍스트를 지원한다.
레이아웃 및 네비게이션
src/components/Layout.tsx, src/components/Navbar.tsx
Outlet을 포함하는 기본 레이아웃과 활성 경로 스타일 전환을 갖춘 4개 링크 Navbar를 추가한다.
페이지 및 라우터
src/App.tsx, src/main.tsx, src/pages/..., src/components/MoviesPage.tsx
App.tsx에서 createBrowserRouter/, /popular, /upcoming, /top-rated, /movies/:movieId 라우트를 구성한다. HomePage는 검색 기능을, MoviesPage는 카테고리별 목록 기능을 담당하며, MovieDetailPage는 라우트 파라미터로 상세를 조회한다.
프로젝트 설정 및 배포
vite.config.ts, tsconfig*.json, eslint.config.js, package.json, vercel.json, index.html, .gitignore, README.md, src/index.css
Vite + React + TailwindCSS 플러그인 설정, 엄격 모드 TypeScript 프로젝트 참조 구성, ESLint flat 설정, Vercel SPA 리라이트 규칙을 추가한다.

Sequence Diagram(s)

sequenceDiagram
  actor User as 사용자
  participant HomePage as HomePage
  participant useMovieSearch as useMovieSearch
  participant useFetch as useFetch
  participant tmdbClient as TMDB API

  User->>HomePage: 검색어 입력 후 제출
  HomePage->>useMovieSearch: submitSearch()
  useMovieSearch->>useMovieSearch: draft → submitted, page=1 리셋
  useMovieSearch->>useFetch: fetcher(searchMovies / fetchMovies) 전달
  useFetch->>tmdbClient: GET /search/movie 또는 /movie/popular
  tmdbClient-->>useFetch: MovieListResponse
  useFetch-->>useMovieSearch: data, isLoading, error
  useMovieSearch-->>HomePage: movies, title, totalPages
  HomePage->>HomePage: MovieGrid 렌더링

  User->>HomePage: 영화 카드 클릭
  HomePage->>HomePage: selectedMovie 상태 설정
  HomePage->>MovieModal: movie, onClose 전달
  MovieModal->>useMovieDetail: useMovieDetail(movie.id)
  useMovieDetail->>tmdbClient: Promise.all([fetchMovieDetail, fetchMovieCredits])
  tmdbClient-->>useMovieDetail: MovieDetail + Credits
  useMovieDetail-->>MovieModal: movie, director, mainCast
  MovieModal->>MovieModal: MovieHero + CastGrid + MovieMetaCards 렌더링
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • HSU-Makeus-Challenge-10th/Web#33: 동일한 ensureToken 패턴, fetchMovies/fetchMovieDetail/fetchMovieCredits 함수 구조, 영화 목록/상세 훅 및 UI 컴포넌트 구현 방식이 이 PR과 코드 수준에서 동일하다.
  • HSU-Makeus-Challenge-10th/Web#39: src/api/tmdb.ts, useFetch/useMovies/useMovieDetail 훅, MoviesPage/MovieGrid/Pagination/MovieModal 컴포넌트 구조가 이 PR의 Week10 구현과 직접적으로 대응된다.

Poem

🐰 토끼가 코드를 후다닥 짰네,
영화를 검색하고 모달도 띄우고,
useFetch로 안전하게 데이터 받아와,
IMDb 링크까지 새 탭으로 열었지.
🎬 버니의 MovieApp, 이제 배포 완료!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주차와 미션 번호를 명확히 포함하고 있어 리포지토리의 명명 규칙을 따르고 있습니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션(미션 번호, 구현 사항)을 포함하고 있으나, 스크린샷과 체크리스트 확인이 완료되지 않았습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gureum/Week10

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@huheyun
huheyun requested a review from wantkdd June 18, 2026 11:02
@huheyun huheyun self-assigned this Jun 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

🧹 Nitpick comments (1)
Week10/gureum/mission1/src/components/movies/Pagination.tsx (1)

18-44: ⚡ Quick win

아이콘 버튼에 접근성 라벨과 type을 추가해 주세요.

현재 <, >만으로는 보조기기에서 의도가 불명확합니다. aria-labeltype="button"을 명시하면 접근성과 안정성이 좋아집니다.

제안 패치
       <button
+        type="button"
+        aria-label="이전 페이지"
         onClick={onPrev}
         disabled={currentPage === 1}
         className={`w-12 h-12 rounded-full font-bold text-xl transition-colors ${
@@
       <button
+        type="button"
+        aria-label="다음 페이지"
         onClick={onNext}
         disabled={currentPage >= totalPages}
         className={`w-12 h-12 rounded-full font-bold text-xl transition-colors ${
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/components/movies/Pagination.tsx` around lines 18
- 44, The pagination buttons in the Pagination component lack proper
accessibility attributes. Add aria-label attributes to both the previous button
(onClick={onPrev}) with text like "Previous page" and the next button
(onClick={onNext}) with text like "Next page" to clarify their purpose for
assistive devices. Additionally, add explicit type="button" attributes to both
button elements to ensure proper HTML semantics and prevent unexpected form
submission behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Week10/gureum/mission1/README.md`:
- Around line 17-19: The VITE_TMDB_API_KEY environment variable with the Bearer
token is being exposed to the client bundle because VITE_ prefixed variables are
automatically bundled into the browser. To fix this security issue, remove the
VITE_TMDB_API_KEY from the client environment variables, move the TMDB Bearer
token to server-side environment variables (not prefixed with VITE_), create a
server-side proxy endpoint or API route that uses the token to make requests to
TMDB, and update the client code to call your server endpoint instead of
directly calling the TMDB API with the token. This way the token remains
protected on the server and the client never has access to it.
- Around line 14-19: The README currently instructs developers to reference a
`.env.example` file for environment variable setup, but this file does not exist
in the repository. Either create a `.env.example` file in the project root that
contains the required environment variable templates (VITE_TMDB_BASE_URL and
VITE_TMDB_API_KEY with placeholder values), or update the README instruction in
lines 14-19 to remove the reference to the non-existent `.env.example` file and
directly provide the environment variable names and format that developers need
to configure.

In `@Week10/gureum/mission1/src/api/tmdb.ts`:
- Around line 11-25: The ensureToken function currently only validates the
TMDB_TOKEN but does not validate BASE_URL, which creates a security risk where
the Authorization header could be exposed to the current web app origin if
BASE_URL is not configured. Modify the ensureToken function to also check that
BASE_URL is properly set, throwing an error with an appropriate message if
BASE_URL is missing or empty, ensuring both required configuration values are
validated before axios client usage.
- Around line 14-19: The tmdbClient axios instance is missing an explicit
timeout configuration, which means network requests can hang indefinitely and
leave the isLoading state in loading status. Add a timeout property to the
axios.create configuration object in tmdbClient to set a reasonable timeout
value (typically in milliseconds, such as 10000 for 10 seconds). This ensures
that requests will fail gracefully after the specified timeout period rather
than hanging indefinitely.

In `@Week10/gureum/mission1/src/components/common/ErrorState.tsx`:
- Around line 15-28: The ErrorState component is enforcing full-screen height
with the min-h-screen class on its outer container div, which causes layout
overflow issues when used within modal dialogs. Remove the min-h-screen class
from the div that currently has className="min-h-screen bg-white text-gray-800
flex items-center justify-center" to allow the component to size naturally based
on its content. Keep the centering flex utilities and other styling, but let the
parent container control the overall layout and height constraints. This makes
the ErrorState component reusable across different contexts like modals and
full-page displays without forcing unwanted screen height.

In `@Week10/gureum/mission1/src/components/common/LoadingSpinner.tsx`:
- Around line 13-20: The LoadingSpinner component uses `min-h-screen` on the
outer div which forces the component to occupy the full viewport height, causing
layout issues when used inside the MovieModal. Remove the `min-h-screen` class
from the outer div's className attribute so the component adapts to its parent
container's size instead of forcing a minimum viewport height.

In `@Week10/gureum/mission1/src/components/movie-detail/CastGrid.tsx`:
- Around line 18-25: The img element in the CastGrid component lacks error
handling for when images fail to load. The current implementation only has a
static fallback URL that may not be reliable. Add an onError event handler to
the img element that sets a valid fallback image source when the TMDB profile
image fails to load, and replace the unreliable `/api/placeholder/150/225`
placeholder with a more robust fallback URL or state management approach to
handle image loading failures gracefully.

In `@Week10/gureum/mission1/src/components/movie-detail/MovieHero.tsx`:
- Around line 95-99: The anchor element with the imdbSearchUrl href uses
target="_blank" to open in a new tab, but the rel attribute only includes
"noreferrer". For security best practices when using target="_blank", add
"noopener" to the rel attribute alongside "noreferrer" to prevent the new page
from accessing the window.opener property. Update the rel attribute value in the
anchor tag to include both "noreferrer" and "noopener".
- Around line 30-32: The onError handler in the MovieHero component uses an API
route `/api/placeholder/300/450` as a fallback image source, which will fail in
a static SPA deployment since API routes are not available. Replace this
fallback path with a valid static asset URL that points to an actual image file
stored in your public or assets directory, such as a placeholder image file that
exists in your project's static resources.

In `@Week10/gureum/mission1/src/components/movies/MovieGrid.tsx`:
- Around line 21-28: The onError handler in the img element within MovieGrid is
setting display: none when the poster fails to load, causing the card to
collapse and the movie item to disappear from the grid. Instead of hiding the
image, modify the error handling to display a fallback placeholder or default
image that maintains the same dimensions as the poster image. This could involve
rendering a default image, placeholder content, or styled fallback element in
the same space where the poster would appear, ensuring the card height and
layout remain consistent even when the actual poster fails to load.

In `@Week10/gureum/mission1/src/components/movies/MovieModal.tsx`:
- Line 25: The aria-labelledby="movie-modal-title" attribute in the MovieModal
component references an id that does not exist within the modal's content,
breaking screen reader accessibility. Fix this by either adding an
id="movie-modal-title" attribute to an actual heading or title element inside
the modal (such as the modal title text), or replace the aria-labelledby
attribute with aria-label and provide a descriptive label string directly to the
dialog element.

In `@Week10/gureum/mission1/src/hooks/useFetch.ts`:
- Around line 26-60: The useFetch hook has two race condition issues: first, the
runFetch function lacks request ordering, so when multiple fetch calls happen
rapidly, older responses can overwrite newer data; second, the
Promise.resolve().then(runFetch) pattern in the useEffect can cause setState
calls on unmounted components if the component unmounts before the microtask
executes. To fix this, introduce a requestIdRef that increments with each fetch
call, then in the try and catch blocks of runFetch, only update state if the
current requestId matches the latest one (ensuring stale responses are ignored).
Additionally, call runFetch directly in the useEffect instead of wrapping it in
Promise.resolve().then() to eliminate the microtask timing issue and ensure the
cleanup function properly prevents state updates.

In `@Week10/gureum/mission1/src/types/movie.ts`:
- Around line 16-23: The Movie interface in the file has poster_path typed as
string, but the TMDB API can return null for image and link-related fields.
Update the Movie interface to reflect the actual API response schema by changing
poster_path to string | null, and similarly update any other optional image or
link fields (like backdrop_path, homepage, tagline if present) to include null
in their type union. This will align the type definition with the null checks
already present in useMovieSearch.ts and MovieHero.tsx, and match the pattern
already used in CastMember's profile_path field.

In `@Week10/gureum/mission1/tsconfig.node.json`:
- Around line 2-25: The compilerOptions in tsconfig.node.json is missing the
"composite": true setting, which is required for TypeScript project references.
Add "composite": true to the compilerOptions object in tsconfig.node.json. Apply
the same fix to tsconfig.app.json by adding "composite": true to its
compilerOptions as well, ensuring both referenced configuration files support
project reference builds.

---

Nitpick comments:
In `@Week10/gureum/mission1/src/components/movies/Pagination.tsx`:
- Around line 18-44: The pagination buttons in the Pagination component lack
proper accessibility attributes. Add aria-label attributes to both the previous
button (onClick={onPrev}) with text like "Previous page" and the next button
(onClick={onNext}) with text like "Next page" to clarify their purpose for
assistive devices. Additionally, add explicit type="button" attributes to both
button elements to ensure proper HTML semantics and prevent unexpected form
submission behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f04f7dfe-67e1-4b56-be34-751feffdcf20

📥 Commits

Reviewing files that changed from the base of the PR and between 8a6238e and a1981be.

⛔ Files ignored due to path filters (3)
  • Week10/gureum/mission1/package-lock.json is excluded by !**/package-lock.json
  • Week10/gureum/mission1/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • Week10/gureum/mission1/public/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (37)
  • Week10/gureum/mission1/.gitignore
  • Week10/gureum/mission1/README.md
  • Week10/gureum/mission1/eslint.config.js
  • Week10/gureum/mission1/index.html
  • Week10/gureum/mission1/package.json
  • Week10/gureum/mission1/src/App.css
  • Week10/gureum/mission1/src/App.tsx
  • Week10/gureum/mission1/src/api/tmdb.ts
  • Week10/gureum/mission1/src/components/Layout.tsx
  • Week10/gureum/mission1/src/components/MoviesPage.tsx
  • Week10/gureum/mission1/src/components/Navbar.tsx
  • Week10/gureum/mission1/src/components/common/ErrorState.tsx
  • Week10/gureum/mission1/src/components/common/LoadingSpinner.tsx
  • Week10/gureum/mission1/src/components/movie-detail/CastGrid.tsx
  • Week10/gureum/mission1/src/components/movie-detail/MovieHero.tsx
  • Week10/gureum/mission1/src/components/movie-detail/MovieMetaCards.tsx
  • Week10/gureum/mission1/src/components/movies/MovieGrid.tsx
  • Week10/gureum/mission1/src/components/movies/MovieModal.tsx
  • Week10/gureum/mission1/src/components/movies/MovieSearchForm.tsx
  • Week10/gureum/mission1/src/components/movies/Pagination.tsx
  • Week10/gureum/mission1/src/hooks/useFetch.ts
  • Week10/gureum/mission1/src/hooks/useMovieDetail.ts
  • Week10/gureum/mission1/src/hooks/useMovieSearch.ts
  • Week10/gureum/mission1/src/hooks/useMovies.ts
  • Week10/gureum/mission1/src/index.css
  • Week10/gureum/mission1/src/main.tsx
  • Week10/gureum/mission1/src/pages/HomePage.tsx
  • Week10/gureum/mission1/src/pages/MovieDetailPage.tsx
  • Week10/gureum/mission1/src/pages/PopularPage.tsx
  • Week10/gureum/mission1/src/pages/TopRatedPage.tsx
  • Week10/gureum/mission1/src/pages/UpcomingPage.tsx
  • Week10/gureum/mission1/src/types/movie.ts
  • Week10/gureum/mission1/tsconfig.app.json
  • Week10/gureum/mission1/tsconfig.json
  • Week10/gureum/mission1/tsconfig.node.json
  • Week10/gureum/mission1/vercel.json
  • Week10/gureum/mission1/vite.config.ts

Comment on lines +14 to +19
`.env` 파일은 Git에 올리지 않습니다. `.env.example`을 참고해 아래 값을 설정해주세요.

```bash
VITE_TMDB_BASE_URL=https://api.themoviedb.org/3
VITE_TMDB_API_KEY=your_tmdb_bearer_token
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== .env.example 존재 확인 =="
fd -HI '^\.env\.example$'

echo
echo "== README 환경변수 키 확인 =="
rg -n 'VITE_TMDB_BASE_URL|VITE_TMDB_API_KEY' Week10/gureum/mission1/README.md -C2

echo
echo "== .env.example 내 키 확인 (파일이 있을 때만) =="
if fd -HI '^\.env\.example$' | grep -q .; then
  fd -HI '^\.env\.example$' -x sh -c 'echo "--- $1 ---"; rg -n "VITE_TMDB_BASE_URL|VITE_TMDB_API_KEY" "$1" -C2 || true' sh {}
fi

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 284


.env.example 파일이 존재하지 않습니다. 파일을 생성하거나 README 지시문을 수정해야 합니다.

README 14번 라인에서는 개발자들이 .env.example을 참고하도록 안내하고 있으나, 해당 파일이 저장소에 존재하지 않습니다. .env.example 파일을 생성하여 필요한 환경 변수를 포함시키거나, README의 참고 지시문을 수정하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/README.md` around lines 14 - 19, The README currently
instructs developers to reference a `.env.example` file for environment variable
setup, but this file does not exist in the repository. Either create a
`.env.example` file in the project root that contains the required environment
variable templates (VITE_TMDB_BASE_URL and VITE_TMDB_API_KEY with placeholder
values), or update the README instruction in lines 14-19 to remove the reference
to the non-existent `.env.example` file and directly provide the environment
variable names and format that developers need to configure.

Comment on lines +17 to +19
VITE_TMDB_BASE_URL=https://api.themoviedb.org/3
VITE_TMDB_API_KEY=your_tmdb_bearer_token
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

클라이언트 번들에 Bearer 토큰을 노출하고 있습니다.

Line 17-19처럼 VITE_ 접두사 환경변수에 TMDB Bearer 토큰을 넣으면 브라우저에서 그대로 노출됩니다. 현재 구조는 키 남용/쿼터 소진 리스크가 있어, 토큰은 서버(프록시/API route)로 이동하고 프론트는 서버 엔드포인트만 호출하도록 바꾸는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/README.md` around lines 17 - 19, The VITE_TMDB_API_KEY
environment variable with the Bearer token is being exposed to the client bundle
because VITE_ prefixed variables are automatically bundled into the browser. To
fix this security issue, remove the VITE_TMDB_API_KEY from the client
environment variables, move the TMDB Bearer token to server-side environment
variables (not prefixed with VITE_), create a server-side proxy endpoint or API
route that uses the token to make requests to TMDB, and update the client code
to call your server endpoint instead of directly calling the TMDB API with the
token. This way the token remains protected on the server and the client never
has access to it.

Comment on lines +11 to +25
const BASE_URL = import.meta.env.VITE_TMDB_BASE_URL;
const TMDB_TOKEN = import.meta.env.VITE_TMDB_API_KEY;

const tmdbClient = axios.create({
baseURL: BASE_URL,
headers: {
Authorization: `Bearer ${TMDB_TOKEN}`,
},
});

const ensureToken = () => {
if (!TMDB_TOKEN) {
throw new Error('TMDB API 토큰이 설정되지 않았습니다.');
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n Week10/gureum/mission1/src/api/tmdb.ts

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 2256


🏁 Script executed:

# Check for env file configuration
find Week10/gureum/mission1 -type f \( -name ".env*" -o -name "vite.config.*" \) | head -20

# Search for other uses of BASE_URL or VITE_TMDB_BASE_URL
rg "VITE_TMDB_BASE_URL|VITE_TMDB_API_KEY" Week10/gureum/mission1 -n

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 463


🏁 Script executed:

# Check vite.config.ts to see if there's any env validation
cat -n Week10/gureum/mission1/vite.config.ts

# Search for any other validation of these env vars
rg "BASE_URL|TMDB_TOKEN" Week10/gureum/mission1 --type ts -B 2 -A 2

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 1364


BASE_URL 미검증으로 인한 인증 토큰 노출 위험

ensureToken()이 TMDB_TOKEN만 검증하고 BASE_URL을 검증하지 않으면, VITE_TMDB_BASE_URL이 비어 있을 경우 axios가 상대 경로로 요청을 전송합니다. 이때 Line 17의 Authorization 헤더가 그대로 포함되어 현재 웹앱 오리진으로 Bearer 토큰이 전송됩니다. 토큰 누락과 동일하게 BASE_URL 누락도 fail-fast 처리가 필요합니다.

제안 수정
-const BASE_URL = import.meta.env.VITE_TMDB_BASE_URL;
-const TMDB_TOKEN = import.meta.env.VITE_TMDB_API_KEY;
+const BASE_URL = import.meta.env.VITE_TMDB_BASE_URL?.trim();
+const TMDB_TOKEN = import.meta.env.VITE_TMDB_API_KEY?.trim();

 const tmdbClient = axios.create({
   baseURL: BASE_URL,
   headers: {
-    Authorization: `Bearer ${TMDB_TOKEN}`,
+    ...(TMDB_TOKEN ? { Authorization: `Bearer ${TMDB_TOKEN}` } : {}),
   },
 });

-const ensureToken = () => {
+const ensureConfig = () => {
+  if (!BASE_URL) {
+    throw new Error('TMDB BASE_URL이 설정되지 않았습니다.');
+  }
   if (!TMDB_TOKEN) {
     throw new Error('TMDB API 토큰이 설정되지 않았습니다.');
   }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/api/tmdb.ts` around lines 11 - 25, The ensureToken
function currently only validates the TMDB_TOKEN but does not validate BASE_URL,
which creates a security risk where the Authorization header could be exposed to
the current web app origin if BASE_URL is not configured. Modify the ensureToken
function to also check that BASE_URL is properly set, throwing an error with an
appropriate message if BASE_URL is missing or empty, ensuring both required
configuration values are validated before axios client usage.

Comment on lines +14 to +19
const tmdbClient = axios.create({
baseURL: BASE_URL,
headers: {
Authorization: `Bearer ${TMDB_TOKEN}`,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate and examine the tmdb.ts file
find . -path "*/Week10/gureum/mission1/src/api/tmdb.ts" -type f 2>/dev/null

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 114


🏁 Script executed:

#!/bin/bash
# Also try broader search in case the path varies
fd -type f -name "tmdb.ts" 2>/dev/null | head -20

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Search for axios.create usage to understand the pattern
rg "axios\.create" --type ts --type tsx -B 2 -A 5 2>/dev/null

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Read the tmdb.ts file to see the actual implementation
cat -n ./Week10/gureum/mission1/src/api/tmdb.ts

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 2256


🏁 Script executed:

#!/bin/bash
# Check if timeout is already configured anywhere in the file
grep -n "timeout" ./Week10/gureum/mission1/src/api/tmdb.ts 2>/dev/null || echo "No timeout found"

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Search for isLoading usage in the codebase to understand how it's used with tmdbClient
rg "isLoading" --type ts --type tsx -B 2 -A 2 2>/dev/null | head -50

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Search more broadly for loading/isLoading patterns in the component files
find ./Week10/gureum/mission1 -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.jsx" -o -name "*.js" \) | xargs grep -l "loading\|Loading" 2>/dev/null | head -10

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Check for async/await usage and error handling patterns
grep -r "await.*tmdbClient\|fetchMovies\|searchMovies\|fetchMovieDetail\|fetchMovieCredits" ./Week10/gureum/mission1/src --include="*.ts" --include="*.tsx" -B 2 -A 3 2>/dev/null | head -80

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 5231


🏁 Script executed:

#!/bin/bash
# Look at component structure to understand loading state management
find ./Week10/gureum/mission1/src -type f -name "*.tsx" -o -name "*.jsx" | head -10 | xargs -I {} sh -c 'echo "=== {} ===" && head -50 {}'

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 12916


🏁 Script executed:

#!/bin/bash
# Check the useFetch hook to understand loading/error handling
cat -n ./Week10/gureum/mission1/src/hooks/useFetch.ts

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 2294


🌐 Web query:

axios default timeout behavior no timeout

💡 Result:

The default timeout value for Axios is 0, which means there is no timeout applied by default [1][2][3]. Consequently, without an explicit timeout configuration, a request can hang indefinitely if the server does not respond [2]. It is highly recommended to set a specific timeout value in production environments to prevent requests from hanging in the event of network issues or server stalls [2]. You can configure this globally, for a specific instance, or on a per-request basis [1][4]. When a timeout is exceeded, Axios will abort the request and typically throw an error with the code ECONNABORTED [2]. You can further refine error handling by configuring the transitional.clarifyTimeoutError setting to true, which can help in distinguishing timeout errors [2].

Citations:


axios 인스턴스에 명시적 timeout 설정이 필요합니다.

axios 기본 timeout 값이 없어(무제한) 네트워크 hang 시 isLoading 상태가 무기한 지속됩니다. 명시적 timeout을 설정하면 이 문제를 방지할 수 있습니다.

🔧 제안 수정
 const tmdbClient = axios.create({
   baseURL: BASE_URL,
+  timeout: 10000,
   headers: {
     Authorization: `Bearer ${TMDB_TOKEN}`,
   },
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const tmdbClient = axios.create({
baseURL: BASE_URL,
headers: {
Authorization: `Bearer ${TMDB_TOKEN}`,
},
});
const tmdbClient = axios.create({
baseURL: BASE_URL,
timeout: 10000,
headers: {
Authorization: `Bearer ${TMDB_TOKEN}`,
},
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/api/tmdb.ts` around lines 14 - 19, The tmdbClient
axios instance is missing an explicit timeout configuration, which means network
requests can hang indefinitely and leave the isLoading state in loading status.
Add a timeout property to the axios.create configuration object in tmdbClient to
set a reasonable timeout value (typically in milliseconds, such as 10000 for 10
seconds). This ensures that requests will fail gracefully after the specified
timeout period rather than hanging indefinitely.

Comment on lines +15 to +28
<div className="min-h-screen bg-white text-gray-800 flex items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4">{icon}</div>
<p className="text-xl text-red-500 mb-4">{message}</p>
{actionLabel && onAction && (
<button
onClick={onAction}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
{actionLabel}
</button>
)}
</div>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

에러 컴포넌트도 min-h-screen 고정으로 모달 UI를 과확장합니다.

ErrorState가 화면 높이를 강제해서 모달 내부 에러 화면이 불필요하게 커지고 스크롤이 생깁니다. 공통 컴포넌트는 full-screen 책임을 가지지 않게 분리해 주세요.

제안 패치
-    <div className="min-h-screen bg-white text-gray-800 flex items-center justify-center">
+    <div className="flex items-center justify-center text-gray-800 py-10">
       <div className="text-center">
         <div className="text-6xl mb-4">{icon}</div>
         <p className="text-xl text-red-500 mb-4">{message}</p>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="min-h-screen bg-white text-gray-800 flex items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4">{icon}</div>
<p className="text-xl text-red-500 mb-4">{message}</p>
{actionLabel && onAction && (
<button
onClick={onAction}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
{actionLabel}
</button>
)}
</div>
</div>
<div className="flex items-center justify-center text-gray-800 py-10">
<div className="text-center">
<div className="text-6xl mb-4">{icon}</div>
<p className="text-xl text-red-500 mb-4">{message}</p>
{actionLabel && onAction && (
<button
onClick={onAction}
className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
{actionLabel}
</button>
)}
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/components/common/ErrorState.tsx` around lines 15
- 28, The ErrorState component is enforcing full-screen height with the
min-h-screen class on its outer container div, which causes layout overflow
issues when used within modal dialogs. Remove the min-h-screen class from the
div that currently has className="min-h-screen bg-white text-gray-800 flex
items-center justify-center" to allow the component to size naturally based on
its content. Keep the centering flex utilities and other styling, but let the
parent container control the overall layout and height constraints. This makes
the ErrorState component reusable across different contexts like modals and
full-page displays without forcing unwanted screen height.

Comment on lines +21 to +28
<img
src={getPosterUrl(movie.poster_path)}
alt={movie.title}
className="w-full h-auto object-cover transition-all duration-300 group-hover:blur-sm group-hover:brightness-50"
onError={(event) => {
event.currentTarget.style.display = 'none';
}}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

포스터 로드 실패 시 카드를 숨겨서 영화 항목이 사라집니다.

onError에서 이미지를 display: none 처리하면 카드 높이가 무너져 해당 영화가 사실상 보이지 않게 됩니다. 실패 시에도 같은 영역에 fallback을 렌더링해야 목록 일관성이 유지됩니다.

제안 패치
-          <img
-            src={getPosterUrl(movie.poster_path)}
-            alt={movie.title}
-            className="w-full h-auto object-cover transition-all duration-300 group-hover:blur-sm group-hover:brightness-50"
-            onError={(event) => {
-              event.currentTarget.style.display = 'none';
-            }}
-          />
+          <div className="aspect-[2/3] w-full bg-gray-200">
+            <img
+              src={getPosterUrl(movie.poster_path)}
+              alt={movie.title}
+              className="h-full w-full object-cover transition-all duration-300 group-hover:blur-sm group-hover:brightness-50"
+              onError={(event) => {
+                event.currentTarget.src =
+                  'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="450"><rect width="100%" height="100%" fill="%23e5e7eb"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="%236b7280" font-size="18">No Image</text></svg>';
+              }}
+            />
+          </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<img
src={getPosterUrl(movie.poster_path)}
alt={movie.title}
className="w-full h-auto object-cover transition-all duration-300 group-hover:blur-sm group-hover:brightness-50"
onError={(event) => {
event.currentTarget.style.display = 'none';
}}
/>
<div className="aspect-[2/3] w-full bg-gray-200">
<img
src={getPosterUrl(movie.poster_path)}
alt={movie.title}
className="h-full w-full object-cover transition-all duration-300 group-hover:blur-sm group-hover:brightness-50"
onError={(event) => {
event.currentTarget.src =
'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="450"><rect width="100%" height="100%" fill="%23e5e7eb"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" fill="%236b7280" font-size="18">No Image</text></svg>';
}}
/>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/components/movies/MovieGrid.tsx` around lines 21 -
28, The onError handler in the img element within MovieGrid is setting display:
none when the poster fails to load, causing the card to collapse and the movie
item to disappear from the grid. Instead of hiding the image, modify the error
handling to display a fallback placeholder or default image that maintains the
same dimensions as the poster image. This could involve rendering a default
image, placeholder content, or styled fallback element in the same space where
the poster would appear, ensuring the card height and layout remain consistent
even when the actual poster fails to load.

className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4 py-8"
role="dialog"
aria-modal="true"
aria-labelledby="movie-modal-title"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

aria-labelledby 참조 대상이 없어 대화상자 제목이 노출되지 않습니다.

Line 25에서 aria-labelledby="movie-modal-title"를 선언했지만, 모달 내부에 해당 id가 없습니다. 스크린리더 접근성에 직접 영향을 줍니다. aria-label을 직접 부여하거나 실제 제목 요소와 연결해 주세요.

수정 예시
-      aria-labelledby="movie-modal-title"
+      aria-label={`${movie.title} 상세 정보`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
aria-labelledby="movie-modal-title"
aria-label={`${movie.title} 상세 정보`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/components/movies/MovieModal.tsx` at line 25, The
aria-labelledby="movie-modal-title" attribute in the MovieModal component
references an id that does not exist within the modal's content, breaking screen
reader accessibility. Fix this by either adding an id="movie-modal-title"
attribute to an actual heading or title element inside the modal (such as the
modal title text), or replace the aria-labelledby attribute with aria-label and
provide a descriptive label string directly to the dialog element.

Comment on lines +26 to +60
const runFetch = useCallback(async () => {
setIsLoading(true);
setError(null);

try {
const nextData = await fetcher();

if (isMountedRef.current) {
setData(nextData);
}
} catch (requestError) {
console.error('데이터 요청 실패:', requestError);

if (isMountedRef.current) {
setError(options?.errorMessage ?? '데이터를 불러오지 못했습니다.');
}
} finally {
if (isMountedRef.current) {
setIsLoading(false);
}
}
}, [fetcher, options?.errorMessage]);

useEffect(() => {
isMountedRef.current = true;

// fetcher 참조가 바뀌면 이 effect가 다시 돌고, 자동 재요청됩니다.
// React Hooks lint가 effect 내부의 동기 setState를 막기 때문에 요청 시작을 한 틱 늦춥니다.
void Promise.resolve().then(runFetch);

return () => {
// 이 시점부터는 "화면에서 내려간 상태"이므로 setState를 막습니다.
isMountedRef.current = false;
};
}, [runFetch]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "useFetch.ts" -type f

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 167


🏁 Script executed:

git ls-files | grep -i usefetch

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 163


🏁 Script executed:

cat -n Week10/gureum/mission1/src/hooks/useFetch.ts

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 2294


요청 경합과 언마운트 타이밍 문제로 인한 상태 오염 위험이 실제로 존재합니다.

두 가지 문제가 있습니다:

  1. 요청 순서 경합: 빠르게 연속 호출되면 느린 이전 요청의 응답이 나중에 도착하여 최신 데이터를 덮어쓸 수 있습니다. runFetch()를 여러 번 호출했을 때 응답 순서를 추적하지 않으면 이 문제가 발생합니다.

  2. 언마운트 타이밍 경합: 라인 54의 Promise.resolve().then(runFetch)로 인해 마이크로태스크 큐에 쌓인 상태에서 컴포넌트가 빠르게 언마운트되면, 정리 함수가 isMountedRef.current = false로 설정한 후에도 마이크로태스크가 실행될 수 있습니다. 이 경우 라인 27~28의 동기 setState 호출이 언마운트된 컴포넌트에서 실행되어 경고를 유발합니다.

제안된 수정(requestIdRef 추가 + 직접 실행)이 두 문제를 모두 해결합니다:

🔧 제안 수정
 import { useCallback, useEffect, useRef, useState } from 'react';
@@
   const isMountedRef = useRef(true);
+  const requestIdRef = useRef(0);
@@
   const runFetch = useCallback(async () => {
+    if (!isMountedRef.current) return;
+    const requestId = ++requestIdRef.current;
+
     setIsLoading(true);
     setError(null);

     try {
       const nextData = await fetcher();

-      if (isMountedRef.current) {
+      if (isMountedRef.current && requestId === requestIdRef.current) {
         setData(nextData);
       }
     } catch (requestError) {
       console.error('데이터 요청 실패:', requestError);

-      if (isMountedRef.current) {
+      if (isMountedRef.current && requestId === requestIdRef.current) {
         setError(options?.errorMessage ?? '데이터를 불러오지 못했습니다.');
       }
     } finally {
-      if (isMountedRef.current) {
+      if (isMountedRef.current && requestId === requestIdRef.current) {
         setIsLoading(false);
       }
     }
   }, [fetcher, options?.errorMessage]);
@@
-    void Promise.resolve().then(runFetch);
+    void runFetch();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/hooks/useFetch.ts` around lines 26 - 60, The
useFetch hook has two race condition issues: first, the runFetch function lacks
request ordering, so when multiple fetch calls happen rapidly, older responses
can overwrite newer data; second, the Promise.resolve().then(runFetch) pattern
in the useEffect can cause setState calls on unmounted components if the
component unmounts before the microtask executes. To fix this, introduce a
requestIdRef that increments with each fetch call, then in the try and catch
blocks of runFetch, only update state if the current requestId matches the
latest one (ensuring stale responses are ignored). Additionally, call runFetch
directly in the useEffect instead of wrapping it in Promise.resolve().then() to
eliminate the microtask timing issue and ensure the cleanup function properly
prevents state updates.

Comment on lines +16 to +23
export interface Movie {
id: number;
title: string;
overview: string;
poster_path: string;
release_date: string;
vote_average: number;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd Week10/gureum/mission1 && find . -type f -name "*.ts" | grep -E "(movie|types)" | head -20

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 94


🏁 Script executed:

cat -n Week10/gureum/mission1/src/types/movie.ts

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 1815


🏁 Script executed:

cat -n Week10/gureum/mission1/src/hooks/useMovieSearch.ts | head -60

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 2345


🏁 Script executed:

grep -r "poster_path\|backdrop_path\|tagline\|homepage" Week10/gureum/mission1/src --include="*.tsx" --include="*.ts" -n | head -30

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 1569


🌐 Web query:

TMDB API Movie response schema nullable fields poster_path backdrop_path

💡 Result:

In the TMDB API, the fields poster_path and backdrop_path are nullable [1]. When a movie does not have an associated image, these fields will be returned as null rather than being omitted from the response [1]. In technical implementations, you should handle these fields as potentially null strings [1]. For example, in many client libraries and TypeScript definitions, they are commonly typed as string | null or optional strings (string?) to account for this behavior [1]. If you are parsing the JSON response, ensure your code checks for null or undefined values before attempting to construct the full image URL, as the API returns only the relative path (e.g., /path/to/image.jpg) [2], which requires appending to a base image URL provided by the TMDB configuration endpoint.

Citations:


🌐 Web query:

TMDB API documentation movie detail response null values fields

💡 Result:

The TMDB API response handling for null values, missing fields, and empty strings follows a flexible pattern rather than a strictly enforced schema. You should design your API integration to be resilient to these variations [1][2][3]. Key points regarding null values and missing fields: 1. Absence of Fields: The API may omit fields entirely from the JSON response if there is no data to return [2]. Relying on the existence of a key without checking for it can lead to runtime errors in your application [2]. Always verify that a field exists before attempting to access it. 2. Null Values: When data is present but explicitly unknown or not set, the API often returns a null value [3]. However, consistency can vary, and some fields may occasionally return empty strings or omit the key entirely depending on the specific endpoint and record [4][2][3]. 3. Append to Response: When using the append_to_response parameter to request additional data (e.g., images, videos), fields that are not explicitly requested will be absent or return as null in your local object model [5][6][7]. Ensure your code handles these cases by checking for null/missing data before processing appended objects [5][7]. 4. Documentation vs. Reality: While official documentation outlines standard response structures, it may not explicitly mark every nullable field [1]. Community discussions indicate that developers should not strictly assume a field is always present or non-null based solely on the documentation [1][2]. Best Practice: Implement robust defensive programming techniques. Check for the existence of keys and validate that values are not null or empty before performing operations on them. Do not treat the absence of a field in documentation as a guarantee that the field will always be present in the response [1][2].

Citations:


TMDB nullable 필드 타입을 실제 응답 스키마에 맞춰야 합니다.

Line 20의 poster_pathstring으로 고정하면 실제 null 응답을 타입이 숨기게 됩니다. 이미 useMovieSearch.ts Line 48에서 .filter((movie) => movie.poster_path)로 null 가능성을 전제로 필터링하고 있고, MovieHero.tsx에서도 movie.tagline &&, movie.homepage &&, movie.backdrop_path ?로 null 체크를 하고 있어 계약이 어긋나 있습니다. TMDB API는 이미지나 텍스트 필드가 없을 때 이들 필드를 null로 반환합니다. CastMember의 profile_path가 이미 string | null로 정의된 것처럼, MovieDetail의 이미지/링크 계열 필드도 null 허용으로 일관되게 맞춰야 다운스트림 렌더링 오류를 줄이고 타입 안전성을 개선할 수 있습니다.

🔧 제안 수정
 export interface Movie {
   id: number;
   title: string;
   overview: string;
-  poster_path: string;
+  poster_path: string | null;
   release_date: string;
   vote_average: number;
 }

 export interface MovieDetail {
   id: number;
   title: string;
   overview: string;
-  poster_path: string;
-  backdrop_path: string;
+  poster_path: string | null;
+  backdrop_path: string | null;
   release_date: string;
   vote_average: number;
   vote_count: number;
   runtime: number;
   genres: Genre[];
   budget: number;
   revenue: number;
-  tagline: string;
-  homepage: string;
+  tagline: string | null;
+  homepage: string | null;
   status: string;
   original_language: string;
   original_title: string;
   popularity: number;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/src/types/movie.ts` around lines 16 - 23, The Movie
interface in the file has poster_path typed as string, but the TMDB API can
return null for image and link-related fields. Update the Movie interface to
reflect the actual API response schema by changing poster_path to string | null,
and similarly update any other optional image or link fields (like
backdrop_path, homepage, tagline if present) to include null in their type
union. This will align the type definition with the null checks already present
in useMovieSearch.ts and MovieHero.tsx, and match the pattern already used in
CastMember's profile_path field.

Comment on lines +2 to +25
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"types": ["node"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import json, pathlib
p = pathlib.Path("Week10/gureum/mission1/package.json")
data = json.loads(p.read_text())
ts = data.get("devDependencies", {}).get("typescript") or data.get("dependencies", {}).get("typescript") or "missing"
print(f"typescript: {ts}")
PY

rg -n '"references"|"composite"|"erasableSyntaxOnly"' Week10/gureum/mission1/tsconfig*.json

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 302


🏁 Script executed:

cat -n Week10/gureum/mission1/tsconfig.json

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 241


🏁 Script executed:

cat -n Week10/gureum/mission1/tsconfig.node.json

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 908


🏁 Script executed:

cat -n Week10/gureum/mission1/tsconfig.app.json

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 1001


tsconfig.node.jsontsconfig.app.json"composite": true를 추가하세요.

tsconfig.json에서 프로젝트 참조(references)를 사용할 때, 참조되는 설정 파일들은 반드시 compilerOptions"composite": true를 포함해야 합니다. 현재 두 파일 모두 이 옵션이 누락되어 있어 tsc -b 실행 시 빌드 오류가 발생할 수 있습니다.

"compilerOptions": {
  "composite": true,
  ...
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week10/gureum/mission1/tsconfig.node.json` around lines 2 - 25, The
compilerOptions in tsconfig.node.json is missing the "composite": true setting,
which is required for TypeScript project references. Add "composite": true to
the compilerOptions object in tsconfig.node.json. Apply the same fix to
tsconfig.app.json by adding "composite": true to its compilerOptions as well,
ensuring both referenced configuration files support project reference builds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant