Create Week10 Mission1,2 - #83
Conversation
📝 WalkthroughWalkthroughReact 19 + TypeScript + Vite 기반의 TMDB 영화 검색 애플리케이션을 신규 추가한다. ChangesTMDB 영화 검색 앱 신규 구성
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)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (6)
Week10/mint0326/mission1/package-lock.jsonis excluded by!**/package-lock.jsonWeek10/mint0326/mission1/public/favicon.svgis excluded by!**/*.svgWeek10/mint0326/mission1/public/icons.svgis excluded by!**/*.svgWeek10/mint0326/mission1/src/assets/hero.pngis excluded by!**/*.pngWeek10/mint0326/mission1/src/assets/react.svgis excluded by!**/*.svgWeek10/mint0326/mission1/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (21)
Week10/mint0326/mission1/.gitignoreWeek10/mint0326/mission1/README.mdWeek10/mint0326/mission1/eslint.config.jsWeek10/mint0326/mission1/index.htmlWeek10/mint0326/mission1/package.jsonWeek10/mint0326/mission1/src/App.cssWeek10/mint0326/mission1/src/App.tsxWeek10/mint0326/mission1/src/components/MovieCard.tsxWeek10/mint0326/mission1/src/components/MovieList.tsxWeek10/mint0326/mission1/src/components/MovieModal.tsxWeek10/mint0326/mission1/src/components/MovieSearch.tsxWeek10/mint0326/mission1/src/index.cssWeek10/mint0326/mission1/src/main.tsxWeek10/mint0326/mission1/src/pages/Home.tsxWeek10/mint0326/mission1/src/pages/MovieDetail.tsxWeek10/mint0326/mission1/src/types.tsWeek10/mint0326/mission1/tsconfig.app.jsonWeek10/mint0326/mission1/tsconfig.jsonWeek10/mint0326/mission1/tsconfig.node.jsonWeek10/mint0326/mission1/vercel.jsonWeek10/mint0326/mission1/vite.config.ts
| - [@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/) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's see the target file
cat -n Week10/mint0326/mission1/README.md | head -20Repository: 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:
- 1: https://www.npmjs.com/package/@vitejs/plugin-react-oxc
- 2: https://registry.npmjs.org/%40vitejs%2Fplugin-react-oxc
- 3: https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-oxc/CHANGELOG.md
- 4: feat: merge
plugin-react-oxcintoplugin-reactvitejs/vite-plugin-react#609 - 5: vitejs/vite-plugin-react@133d786
- 6: https://github.com/vitejs/vite-plugin-react/blob/HEAD/packages/plugin-react/CHANGELOG.md
- 7: https://github.com/vitejs/vite-plugin-react/releases/tag/plugin-react%405.0.0-beta.0
- 8: https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md
- 9: https://vite.dev/plugins/
🌐 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:
- 1: https://github.com/vitejs/vite-plugin-react
- 2: https://github.com/vitejs/vite-plugin-react/blob/main/README.md
- 3: https://github.com/vitejs/vite-plugin-react/tree/44cbed4d00d48331d9757085fae79807dc1a3969
- 4: https://github.com/vitejs/vite-plugin-react/tree/23db72731b7c9c3d57a8188f0395d2ec90a6e2f6
- 5: https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react
🏁 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 -50Repository: 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
| <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> |
There was a problem hiding this comment.
클릭 가능한 카드가 키보드 접근성을 보장하지 못합니다.
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.
| <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.
| const handleImdbSearch = () => { | ||
| window.open(`https://www.imdb.com/find?q=${encodeURIComponent(movie.title)}`, '_blank'); | ||
| }; |
There was a problem hiding this comment.
새 탭 열기 시 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> |
There was a problem hiding this comment.
인기도 바 너비 계산식이 잘못되어 값이 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.
| <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.
| :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; | ||
| } |
There was a problem hiding this comment.
font-family 선언이 현재 Stylelint 규칙과 충돌합니다.
Line 10에서 declaration-empty-line-before 및 font-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.
| :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
| 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; |
There was a problem hiding this comment.
키프레임 이름이 네이밍 규칙(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
| 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}` | ||
| } |
There was a problem hiding this comment.
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.
| 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); | ||
| } |
There was a problem hiding this comment.
검색 요청의 실패 처리와 최신성 보장이 부족합니다.
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.
📝 미션 번호
10주차 Misson 1,2
📋 구현 사항
Mission1
useCallabck으로 이벤트 핸들러 참조 고정useMemo로 계산 비용이 큰 값 메모이제이션memo로 리렌더링이 필요 없는 컴포넌트 메모이제이션Mission 2
📎 스크린샷
Mission1
2026-06-16.231004_1.mp4
Mission2
vercel 배포 사이트 링크
✅ 체크리스트
🤔 질문 사항
Summary by CodeRabbit
신규 기능