Skip to content

Create Week10 Mission1,2 - #83

Open
mint0326 wants to merge 2 commits into
mainfrom
mint0326/week10
Open

Create Week10 Mission1,2#83
mint0326 wants to merge 2 commits into
mainfrom
mint0326/week10

Conversation

@mint0326

@mint0326 mint0326 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

📝 미션 번호

10주차 Misson 1,2

📋 구현 사항

Mission1

  • 영화 사이트 렌더링 최적화
    • useCallabck으로 이벤트 핸들러 참조 고정
    • useMemo로 계산 비용이 큰 값 메모이제이션
    • memo로 리렌더링이 필요 없는 컴포넌트 메모이제이션

Mission 2

  • vercel 배포

📎 스크린샷

Mission1

2026-06-16.231004_1.mp4

Mission2
vercel 배포 사이트 링크

✅ 체크리스트

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

🤔 질문 사항

Summary by CodeRabbit

신규 기능

  • 영화 검색 - 제목으로 영화를 검색하고 결과를 카드 형식으로 확인할 수 있습니다.
  • 상세 정보 모달 - 영화 선택 시 포스터, 평점, 개봉일, 줄거리 등의 정보를 표시합니다.
  • IMDb 연동 - 상세 정보에서 IMDb 검색으로 직접 이동 가능합니다.
  • 검색 필터 - 성인 콘텐츠 포함 여부 및 언어(한/영/일) 선택이 가능합니다.
  • 반응형 설계 - 다양한 화면 크기에 최적화된 사용자 경험을 제공합니다.

@mint0326
mint0326 requested a review from wantkdd June 16, 2026 14:38
@mint0326 mint0326 self-assigned this Jun 16, 2026
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

React 19 + TypeScript + Vite 기반의 TMDB 영화 검색 애플리케이션을 신규 추가한다. Movie 타입 정의, MovieSearch/MovieCard/MovieList/MovieModal 컴포넌트, Home·MovieDetail 페이지, 전역 CSS, 프로젝트 설정 파일(Vite, TypeScript, ESLint, Vercel) 전체가 새로 구성된다.

Changes

TMDB 영화 검색 앱 신규 구성

Layer / File(s) Summary
프로젝트 설정 및 빌드 인프라
package.json, vite.config.ts, tsconfig*.json, eslint.config.js, .gitignore, index.html, vercel.json, README.md
React 19 · React Router 7 · TypeScript 5.9 · Vite 8 기반 의존성을 정의하고, TypeScript/ESLint flat 설정, SPA Vercel 리라이트 규칙, HTML 진입점을 구성한다.
Movie 타입 계약 및 앱 진입점·라우팅
src/types.ts, src/main.tsx, src/App.tsx, src/App.css
Movie 인터페이스(7개 필드) 추가, BrowserRouter로 앱 부트스트랩, /Home, /movies/:movieIdMovieDetail 두 라우트 연결.
검색·목록·카드·모달 컴포넌트
src/components/MovieSearch.tsx, src/components/MovieCard.tsx, src/components/MovieList.tsx, src/components/MovieModal.tsx
검색 폼(쿼리/성인/언어 필터), 단일 카드(포스터·평점·제목), 목록(빈 상태 처리), 상세 모달(IMDb 연결·인기도 바)을 각각 memo로 구현한다.
Home 페이지 – TMDB API 연동 및 상태 관리
src/pages/Home.tsx
fetchMovies(useCallback)로 TMDB /search/movie를 호출하고, 로딩 상태·평균 평점(useMemo)·영화 클릭 라우팅(useNavigate)을 관리하며 하위 컴포넌트에 상태/콜백을 전달한다.
MovieDetail 페이지
src/pages/MovieDetail.tsx
useParamsmovieId를 읽어 표시하고, useNavigate(-1)로 뒤로 가기 버튼을 구현한다.
전체 스타일시트
src/index.css
전역 CSS 변수/리셋, 검색 폼, 카드 그리드·호버, 모달 레이아웃, fadeIn·slideUp 애니메이션, 모바일 반응형 미디어 쿼리(768px 이하)를 추가한다.

Sequence Diagram(s)

sequenceDiagram
  actor 사용자
  participant MovieSearch
  participant Home
  participant TMDB_API as TMDB API
  participant MovieList
  participant MovieCard
  participant MovieModal

  사용자->>MovieSearch: 검색어/필터 입력 후 제출
  MovieSearch->>Home: onSearch() 호출
  Home->>TMDB_API: GET /search/movie?query=...&api_key=...
  TMDB_API-->>Home: { results: Movie[] }
  Home->>MovieList: movies[] 전달
  MovieList->>MovieCard: movie, onClick 전달 (map)
  사용자->>MovieCard: 카드 클릭
  MovieCard->>Home: onClick(movie)
  Home->>Home: navigate(/movies/:movieId)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • HSU-Makeus-Challenge-10th/Web#30: 동일한 React/Vite 스캐폴딩과 TMDB 영화 UI 흐름(MovieCard 기반 목록, /movies/:movieId 라우팅)을 신규 컴포넌트/설정 파일로 추가한다.
  • HSU-Makeus-Challenge-10th/Web#29: 동일한 Vite/React 템플릿 스캐폴딩(eslint.config.js, vite.config.ts, .gitignore, README 등)을 추가하는 PR이다.
  • HSU-Makeus-Challenge-10th/Web#28: 동일한 ESLint flat config(export default defineConfig(...)) 구조 및 Vite+React 템플릿 설정 파일을 공유한다.

Poem

🐇 토끼가 영화를 검색하네,
TMDB 키로 /search/movie 두드리고,
memo로 리렌더 쏙 막으면서,
카드들이 그리드 위에 팡팡 피어나고,
모달 열면 IMDb까지 달려가누나! 🎬✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 'Create Week10 Mission1,2'는 주요 변경사항(Week10의 Mission1과 2 구현)을 명확하고 간결하게 요약하고 있습니다.
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.
Description check ✅ Passed PR 설명이 템플릿의 모든 필수 섹션을 포함하고 있으며, 미션 번호, 구현 사항, 스크린샷, 체크리스트가 작성되어 있습니다.

✏️ 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 mint0326/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.

@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: 8

🤖 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/mint0326/mission1/README.md`:
- Around line 7-8: The description of `@vitejs/plugin-react` is inaccurate
because it states the plugin "uses Oxc" when in fact Oxc integration is only
conditionally used in specific environments and is not the default
implementation. Replace the misleading description with an accurate one that
explains `@vitejs/plugin-react` is the basic Vite plugin for React development
with Fast Refresh support, removing the blanket statement about using Oxc since
it's not the primary/default behavior of the plugin. Leave the
`@vitejs/plugin-react-swc` description unchanged as it correctly identifies SWC
as the implementation used by that plugin.

In `@Week10/mint0326/mission1/src/components/MovieCard.tsx`:
- Around line 17-27: The outer container element with className="movie-card" is
currently a div with only an onClick handler, which prevents keyboard users from
accessing it via Enter or Space keys. Change this div element to a button
element to ensure proper keyboard accessibility for interactive elements. Update
both the opening tag (from div to button) and the closing tag at the end of the
component (from </div> to </button>), while keeping the className attribute and
onClick handler intact.

In `@Week10/mint0326/mission1/src/components/MovieModal.tsx`:
- Around line 18-20: The handleImdbSearch function uses window.open with
'_blank' target but omits the windowFeatures parameter containing security
flags, creating a tab nabbing vulnerability where the opened page can access
window.opener. Add a third parameter to the window.open call in handleImdbSearch
that includes 'noopener,noreferrer' to prevent the opened page from accessing
the opener reference and mitigate this security risk.
- Line 39: The popularity bar width calculation in the MovieModal component is
incorrectly dividing the popularity value by 100, causing a popularity of 80 to
render as 0.8% instead of 80%. Fix the style calculation for the popularity-fill
div by removing the division by 100 from the expression. Change the formula from
dividing movie.popularity by 100 to using movie.popularity directly in the
Math.min function, since the popularity value is already on a 0-100 scale that
can be directly used as a percentage value.

In `@Week10/mint0326/mission1/src/index.css`:
- Around line 223-235: The keyframe animation names fadeIn and slideUp are using
camelCase instead of the required kebab-case naming convention (fade-in and
slide-up), which violates the keyframes-name-pattern rule. At lines 223-235 in
the .modal-content class, replace the animation property references from fadeIn
to fade-in and slideUp to slide-up. Additionally, at lines 365-373, update the
corresponding `@keyframes` declarations to use `@keyframes` fade-in and `@keyframes`
slide-up instead of `@keyframes` fadeIn and `@keyframes` slideUp to maintain
consistency across the entire stylesheet.
- Around line 1-11: The font-family declaration in the :root selector violates
two Stylelint rules: declaration-empty-line-before and font-family-name-quotes.
Add a blank line before the font-family property (after the --modal-overlay
line) to satisfy the declaration-empty-line-before rule, and remove the single
quotes around Inter in the font-family value to satisfy the
font-family-name-quotes rule. The final font-family declaration should have
Inter without quotes as the first font in the stack.

In `@Week10/mint0326/mission1/src/pages/Home.tsx`:
- Around line 17-36: The fetchMovies function is directly exposing the TMDB API
key in the Authorization header when making requests from the client-side
browser, creating a security vulnerability where the token can be intercepted or
misused. Remove the direct TMDB API call from the fetchMovies function and
instead create a backend endpoint (or serverless proxy) that handles the TMDB
request using the API key stored securely on the server side. Update fetchMovies
to call this backend endpoint with just the query parameters, removing the
Authorization header and the direct TMDB API key reference from the client code.
Move the VITE_TMDB_API_KEY environment variable from client-side to server-side
configuration where it cannot be exposed to the browser.
- Around line 20-49: The fetchMovies function has two critical issues: it lacks
HTTP response status validation and doesn't handle race conditions from
concurrent requests. First, add a check for response.ok after the fetch call to
properly handle 4xx/5xx HTTP errors instead of treating them as successful
responses. Second, implement a race condition prevention mechanism by either
using an AbortController to cancel pending requests when a new search is
initiated, or by tracking the current query/request timestamp and ignoring
responses that arrive after a newer search has been made. This ensures that
stale responses from earlier searches don't overwrite results from the most
recent search request.
🪄 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: a53aef67-bd4b-4d04-9346-1b47d66d56bb

📥 Commits

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

⛔ Files ignored due to path filters (6)
  • Week10/mint0326/mission1/package-lock.json is excluded by !**/package-lock.json
  • Week10/mint0326/mission1/public/favicon.svg is excluded by !**/*.svg
  • Week10/mint0326/mission1/public/icons.svg is excluded by !**/*.svg
  • Week10/mint0326/mission1/src/assets/hero.png is excluded by !**/*.png
  • Week10/mint0326/mission1/src/assets/react.svg is excluded by !**/*.svg
  • Week10/mint0326/mission1/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (21)
  • Week10/mint0326/mission1/.gitignore
  • Week10/mint0326/mission1/README.md
  • Week10/mint0326/mission1/eslint.config.js
  • Week10/mint0326/mission1/index.html
  • Week10/mint0326/mission1/package.json
  • Week10/mint0326/mission1/src/App.css
  • Week10/mint0326/mission1/src/App.tsx
  • Week10/mint0326/mission1/src/components/MovieCard.tsx
  • Week10/mint0326/mission1/src/components/MovieList.tsx
  • Week10/mint0326/mission1/src/components/MovieModal.tsx
  • Week10/mint0326/mission1/src/components/MovieSearch.tsx
  • Week10/mint0326/mission1/src/index.css
  • Week10/mint0326/mission1/src/main.tsx
  • Week10/mint0326/mission1/src/pages/Home.tsx
  • Week10/mint0326/mission1/src/pages/MovieDetail.tsx
  • Week10/mint0326/mission1/src/types.ts
  • Week10/mint0326/mission1/tsconfig.app.json
  • Week10/mint0326/mission1/tsconfig.json
  • Week10/mint0326/mission1/tsconfig.node.json
  • Week10/mint0326/mission1/vercel.json
  • Week10/mint0326/mission1/vite.config.ts

Comment on lines +7 to +8
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)

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

🧩 Analysis chain

🏁 Script executed:

# First, let's see the target file
cat -n Week10/mint0326/mission1/README.md | head -20

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

Length of output: 1097


🌐 Web query:

@vitejs/plugin-react Oxc implementation official documentation

💡 Result:

The package @vitejs/plugin-react-oxc is officially deprecated [1][2][3]. Its functionality has been merged into the core @vitejs/plugin-react plugin [4][5]. Key points regarding the implementation: 1. Deprecation: Users should no longer use @vitejs/plugin-react-oxc. Instead, they should use @vitejs/plugin-react, which automatically handles the Oxc-based integration [1][2][5]. 2. Oxc Integration: When @vitejs/plugin-react is used with rolldown-vite, it automatically utilizes the Oxc Transformer for the React Fast Refresh transform [6][5][7]. This functionality was previously exclusive to the deprecated plugin [6][5]. 3. Official Documentation: The primary source of truth for using React with Vite is now the standard documentation for @vitejs/plugin-react [8][9]. The documentation notes that @vitejs/plugin-react provides React Fast Refresh support via the Oxc Transformer [9]. In summary, there is no longer a separate official Oxc implementation plugin to maintain or document; the integration is native to the primary React plugin when the environment (such as rolldown-vite) supports it [6][4][7].

Citations:


🌐 Web query:

vite-plugin-react GitHub official README

💡 Result:

The official GitHub repository for Vite's React plugins is located at https://github.com/vitejs/vite-plugin-react [1]. This is an all-in-one repository that houses several official plugins for React development within the Vite ecosystem [2][1]. Key information from the repository: - Official Packages: The repository manages multiple packages, including @vitejs/plugin-react (the default plugin for React projects) and @vitejs/plugin-react-swc (a faster alternative using SWC) [2][3]. It also hosts @vitejs/plugin-rsc for React Server Components [2][1]. - Documentation: The README in the main repository directs users to the specific documentation for each package (e.g., @vitejs/plugin-react and @vitejs/plugin-react-swc) to get started with configuration and usage [2][4]. - Deprecations: The @vitejs/plugin-react-oxc package has been deprecated and merged into @vitejs/plugin-react [2][3]. - License: The project is licensed under the MIT License [3][1]. You can access the main repository and its specific package directories directly on GitHub [5][1].

Citations:


🏁 Script executed:

# Check the official README from the referenced link to understand current implementation
curl -s "https://raw.githubusercontent.com/vitejs/vite-plugin-react/main/packages/plugin-react/README.md" | head -50

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

Length of output: 1493


@vitejs/plugin-react 설명을 수정해 주세요.

공식 문서에 따르면 이 플러그인은 Fast Refresh를 통한 React 개발을 위한 기본 Vite 플러그인이며, Oxc는 기본 구현이 아닙니다. Oxc 통합은 rolldown-vite 같은 특정 환경에서만 조건부로 사용되므로, 여기서 "uses Oxc"라고 표현하면 플러그인의 실제 동작과 어긋납니다. (github.com/vitejs/vite-plugin-react)

🤖 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/mint0326/mission1/README.md` around lines 7 - 8, The description of
`@vitejs/plugin-react` is inaccurate because it states the plugin "uses Oxc"
when in fact Oxc integration is only conditionally used in specific environments
and is not the default implementation. Replace the misleading description with
an accurate one that explains `@vitejs/plugin-react` is the basic Vite plugin
for React development with Fast Refresh support, removing the blanket statement
about using Oxc since it's not the primary/default behavior of the plugin. Leave
the `@vitejs/plugin-react-swc` description unchanged as it correctly identifies
SWC as the implementation used by that plugin.

Source: MCP tools

Comment on lines +17 to +27
<div className="movie-card" onClick={() => onClick(movie)}>
<div className="poster-wrapper">
<img src={imageUrl} alt={movie.title} className="poster-image" />
<div className="vote-badge">{movie.vote_average.toFixed(1)}</div>
</div>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<p className="movie-date">{movie.release_date}</p>
<p className="movie-overview">{movie.overview}</p>
</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

클릭 가능한 카드가 키보드 접근성을 보장하지 못합니다.

Line 17에서 div에 클릭만 연결되어 있어 키보드 사용자(Enter/Space) 접근이 막힙니다. 인터랙티브 요소는 button으로 바꾸는 것이 안전합니다.

수정 예시
-    <div className="movie-card" onClick={() => onClick(movie)}>
+    <button
+      type="button"
+      className="movie-card"
+      onClick={() => onClick(movie)}
+      aria-label={`${movie.title} 상세 보기`}
+    >
       <div className="poster-wrapper">
         <img src={imageUrl} alt={movie.title} className="poster-image" />
         <div className="vote-badge">{movie.vote_average.toFixed(1)}</div>
       </div>
       <div className="movie-info">
         <h3 className="movie-title">{movie.title}</h3>
         <p className="movie-date">{movie.release_date}</p>
         <p className="movie-overview">{movie.overview}</p>
       </div>
-    </div>
+    </button>
📝 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="movie-card" onClick={() => onClick(movie)}>
<div className="poster-wrapper">
<img src={imageUrl} alt={movie.title} className="poster-image" />
<div className="vote-badge">{movie.vote_average.toFixed(1)}</div>
</div>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<p className="movie-date">{movie.release_date}</p>
<p className="movie-overview">{movie.overview}</p>
</div>
</div>
<button
type="button"
className="movie-card"
onClick={() => onClick(movie)}
aria-label={`${movie.title} 상세 보기`}
>
<div className="poster-wrapper">
<img src={imageUrl} alt={movie.title} className="poster-image" />
<div className="vote-badge">{movie.vote_average.toFixed(1)}</div>
</div>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<p className="movie-date">{movie.release_date}</p>
<p className="movie-overview">{movie.overview}</p>
</div>
</button>
🤖 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/mint0326/mission1/src/components/MovieCard.tsx` around lines 17 - 27,
The outer container element with className="movie-card" is currently a div with
only an onClick handler, which prevents keyboard users from accessing it via
Enter or Space keys. Change this div element to a button element to ensure
proper keyboard accessibility for interactive elements. Update both the opening
tag (from div to button) and the closing tag at the end of the component (from
</div> to </button>), while keeping the className attribute and onClick handler
intact.

Comment on lines +18 to +20
const handleImdbSearch = () => {
window.open(`https://www.imdb.com/find?q=${encodeURIComponent(movie.title)}`, '_blank');
};

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

새 탭 열기 시 noopener,noreferrer 누락으로 탭내빙 위험이 있습니다.

Line 19의 window.open(..., '_blank')는 opener를 남겨 보안 리스크가 생깁니다.

수정 예시
-    window.open(`https://www.imdb.com/find?q=${encodeURIComponent(movie.title)}`, '_blank');
+    window.open(
+      `https://www.imdb.com/find?q=${encodeURIComponent(movie.title)}`,
+      '_blank',
+      'noopener,noreferrer'
+    );
🤖 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/mint0326/mission1/src/components/MovieModal.tsx` around lines 18 - 20,
The handleImdbSearch function uses window.open with '_blank' target but omits
the windowFeatures parameter containing security flags, creating a tab nabbing
vulnerability where the opened page can access window.opener. Add a third
parameter to the window.open call in handleImdbSearch that includes
'noopener,noreferrer' to prevent the opened page from accessing the opener
reference and mitigate this security risk.

<div className="meta-item">
<span>인기도</span>
<div className="popularity-bar">
<div className="popularity-fill" style={{ width: `${Math.min(movie.popularity / 100, 100)}%` }}></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

인기도 바 너비 계산식이 잘못되어 값이 100배 축소됩니다.

Line 39에서 movie.popularity / 100 뒤에 %를 붙여 80 인기값이 0.8%로 렌더링됩니다.

수정 예시
-                <div className="popularity-fill" style={{ width: `${Math.min(movie.popularity / 100, 100)}%` }}></div>
+                <div
+                  className="popularity-fill"
+                  style={{ width: `${Math.min(movie.popularity, 100)}%` }}
+                ></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
<div className="popularity-fill" style={{ width: `${Math.min(movie.popularity / 100, 100)}%` }}></div>
<div
className="popularity-fill"
style={{ width: `${Math.min(movie.popularity, 100)}%` }}
></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/mint0326/mission1/src/components/MovieModal.tsx` at line 39, The
popularity bar width calculation in the MovieModal component is incorrectly
dividing the popularity value by 100, causing a popularity of 80 to render as
0.8% instead of 80%. Fix the style calculation for the popularity-fill div by
removing the division by 100 from the expression. Change the formula from
dividing movie.popularity by 100 to using movie.popularity directly in the
Math.min function, since the popularity value is already on a 0-100 scale that
can be directly used as a percentage value.

Comment on lines +1 to +11
:root {
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--bg-color: #f3f4f6;
--card-bg: #ffffff;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
--modal-overlay: rgba(0, 0, 0, 0.7);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

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

font-family 선언이 현재 Stylelint 규칙과 충돌합니다.

Line 10에서 declaration-empty-line-beforefont-family-name-quotes 오류가 동시에 발생합니다. 빈 줄 추가와 Inter 따옴표 제거로 린트 오류를 해소해 주세요.

제안 수정안
 :root {
   --primary-color: `#3b82f6`;
   --primary-hover: `#2563eb`;
   --bg-color: `#f3f4f6`;
   --card-bg: `#ffffff`;
   --text-primary: `#1f2937`;
   --text-secondary: `#6b7280`;
   --border-color: `#e5e7eb`;
   --modal-overlay: rgba(0, 0, 0, 0.7);
+
-  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
 }
📝 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
:root {
--primary-color: #3b82f6;
--primary-hover: #2563eb;
--bg-color: #f3f4f6;
--card-bg: #ffffff;
--text-primary: #1f2937;
--text-secondary: #6b7280;
--border-color: #e5e7eb;
--modal-overlay: rgba(0, 0, 0, 0.7);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
:root {
--primary-color: `#3b82f6`;
--primary-hover: `#2563eb`;
--bg-color: `#f3f4f6`;
--card-bg: `#ffffff`;
--text-primary: `#1f2937`;
--text-secondary: `#6b7280`;
--border-color: `#e5e7eb`;
--modal-overlay: rgba(0, 0, 0, 0.7);
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
🧰 Tools
🪛 Stylelint (17.13.0)

[error] 10-10: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 10-10: Expected no quotes around "Inter" (font-family-name-quotes)

(font-family-name-quotes)

🤖 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/mint0326/mission1/src/index.css` around lines 1 - 11, The font-family
declaration in the :root selector violates two Stylelint rules:
declaration-empty-line-before and font-family-name-quotes. Add a blank line
before the font-family property (after the --modal-overlay line) to satisfy the
declaration-empty-line-before rule, and remove the single quotes around Inter in
the font-family value to satisfy the font-family-name-quotes rule. The final
font-family declaration should have Inter without quotes as the first font in
the stack.

Source: Linters/SAST tools

Comment on lines +223 to +235
animation: fadeIn 0.2s ease-out;
}

.modal-content {
background: var(--card-bg);
border-radius: 16px;
display: flex;
max-width: 800px;
width: 100%;
max-height: 90vh;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
animation: slideUp 0.3s ease-out;

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

키프레임 이름이 네이밍 규칙(kebab-case)을 위반합니다.

Lines 223, 235에서 사용하는 fadeIn/slideUp 및 Lines 365, 370 선언부를 kebab-case로 통일해야 keyframes-name-pattern 오류를 해결할 수 있습니다.

제안 수정안
 .modal-overlay {
@@
-  animation: fadeIn 0.2s ease-out;
+  animation: fade-in 0.2s ease-out;
 }
@@
 .modal-content {
@@
-  animation: slideUp 0.3s ease-out;
+  animation: slide-up 0.3s ease-out;
 }
@@
-@keyframes fadeIn {
+@keyframes fade-in {
   from { opacity: 0; }
   to { opacity: 1; }
 }
 
-@keyframes slideUp {
+@keyframes slide-up {
   from { opacity: 0; transform: translateY(20px); }
   to { opacity: 1; transform: translateY(0); }
 }

Also applies to: 365-373

🤖 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/mint0326/mission1/src/index.css` around lines 223 - 235, The keyframe
animation names fadeIn and slideUp are using camelCase instead of the required
kebab-case naming convention (fade-in and slide-up), which violates the
keyframes-name-pattern rule. At lines 223-235 in the .modal-content class,
replace the animation property references from fadeIn to fade-in and slideUp to
slide-up. Additionally, at lines 365-373, update the corresponding `@keyframes`
declarations to use `@keyframes` fade-in and `@keyframes` slide-up instead of
`@keyframes` fadeIn and `@keyframes` slideUp to maintain consistency across the
entire stylesheet.

Source: Linters/SAST tools

Comment on lines +17 to +36
const API_KEY = import.meta.env.VITE_TMDB_API_KEY;

// 영화 검색 API 호출 함수 - useCallback으로 참조 고정
const fetchMovies = useCallback(async () => {
if (!query.trim()) return;

if (!API_KEY) {
alert("TMDB API Key가 설정되지 않았습니다. .env 파일을 확인해주세요.");
return;
}

setIsLoading(true);
try {
const url = `https://api.themoviedb.org/3/search/movie?query=${encodeURIComponent(query)}&include_adult=${includeAdult}&language=${language}&page=1`;

const response = await fetch(url, {
headers: {
accept: 'application/json',
Authorization: `Bearer ${API_KEY}`
}

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

TMDB Bearer 토큰을 클라이언트에서 직접 노출하고 있습니다.

VITE_ 환경변수 + 브라우저 요청 헤더로 Bearer 토큰이 그대로 노출되어 재사용/남용 위험이 있습니다. 검색 호출은 서버(또는 서버리스 프록시)로 우회하고 서버 측 비밀로 TMDB를 호출하는 구조가 필요합니다.

🤖 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/mint0326/mission1/src/pages/Home.tsx` around lines 17 - 36, The
fetchMovies function is directly exposing the TMDB API key in the Authorization
header when making requests from the client-side browser, creating a security
vulnerability where the token can be intercepted or misused. Remove the direct
TMDB API call from the fetchMovies function and instead create a backend
endpoint (or serverless proxy) that handles the TMDB request using the API key
stored securely on the server side. Update fetchMovies to call this backend
endpoint with just the query parameters, removing the Authorization header and
the direct TMDB API key reference from the client code. Move the
VITE_TMDB_API_KEY environment variable from client-side to server-side
configuration where it cannot be exposed to the browser.

Comment on lines +20 to +49
const fetchMovies = useCallback(async () => {
if (!query.trim()) return;

if (!API_KEY) {
alert("TMDB API Key가 설정되지 않았습니다. .env 파일을 확인해주세요.");
return;
}

setIsLoading(true);
try {
const url = `https://api.themoviedb.org/3/search/movie?query=${encodeURIComponent(query)}&include_adult=${includeAdult}&language=${language}&page=1`;

const response = await fetch(url, {
headers: {
accept: 'application/json',
Authorization: `Bearer ${API_KEY}`
}
});

const data = await response.json();
if (data.results) {
setMovies(data.results);
} else {
setMovies([]);
}
} catch (error) {
console.error('Failed to fetch movies:', error);
} finally {
setIsLoading(false);
}

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

검색 요청의 실패 처리와 최신성 보장이 부족합니다.

response.ok 검증이 없어 4xx/5xx를 정상 흐름처럼 처리하고, 동시 검색 시 늦게 도착한 이전 응답이 최신 결과를 덮어쓸 수 있습니다.

수정 예시
-import { useState, useCallback, useMemo } from 'react';
+import { useState, useCallback, useMemo, useRef } from 'react';
@@
   const [isLoading, setIsLoading] = useState(false);
+  const requestSeqRef = useRef(0);
@@
   const fetchMovies = useCallback(async () => {
@@
+    const reqSeq = ++requestSeqRef.current;
     setIsLoading(true);
     try {
@@
       const response = await fetch(url, {
         headers: {
           accept: 'application/json',
           Authorization: `Bearer ${API_KEY}`
         }
       });
+      if (!response.ok) {
+        throw new Error(`TMDB request failed: ${response.status}`);
+      }
       
       const data = await response.json();
+      if (reqSeq !== requestSeqRef.current) return;
       if (data.results) {
         setMovies(data.results);
       } else {
         setMovies([]);
       }
@@
-      setIsLoading(false);
+      if (reqSeq === requestSeqRef.current) {
+        setIsLoading(false);
+      }
     }
   }, [query, includeAdult, language, API_KEY]);
🤖 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/mint0326/mission1/src/pages/Home.tsx` around lines 20 - 49, The
fetchMovies function has two critical issues: it lacks HTTP response status
validation and doesn't handle race conditions from concurrent requests. First,
add a check for response.ok after the fetch call to properly handle 4xx/5xx HTTP
errors instead of treating them as successful responses. Second, implement a
race condition prevention mechanism by either using an AbortController to cancel
pending requests when a new search is initiated, or by tracking the current
query/request timestamp and ignoring responses that arrive after a newer search
has been made. This ensures that stale responses from earlier searches don't
overwrite results from the most recent search request.

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