Skip to content

Create Week10 Mission1, 2 - #82

Open
SJ01-max wants to merge 2 commits into
mainfrom
SJ01-max/week10
Open

SJ01-max wants to merge 2 commits into
mainfrom
SJ01-max/week10

Conversation

@SJ01-max

@SJ01-max SJ01-max commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

📝 미션 번호

10주차 practice01
10주차 practice02
10주차 Misson 1

📋 구현 사항

  • count, text state를 부모(UseCallbackPage)에서 관리
  • CountButton, TextInput에 React.memo 적용
  • CountButton의 onClick은 useCallback으로 감싸 참조 고정, TextInput의 onChange는 일반 함수로 둬서 리렌더링 차이 비교
  • 각 컴포넌트에 렌더링 로그 추가해 콘솔로 동작 확인 가능

📎 스크린샷

image

📝 미션 번호

10주차 practice02

📋 구현 사항

  • utils/math.ts에 isPrime, findPrimeNumbers(에라토스테네스의 체) 구현
  • 입력 숫자(number)를 의존성으로 useMemo로 소수 리스트 계산, "소수 계산 실행" 로그로 재계산 여부 확인
  • 계산과 무관한 note state를 따로 둬서, 그 값이 바뀌어도 useMemo가 재계산되지 않음을 검증

📎 스크린샷

image

📝 미션 번호

10주차 Misson 1

📋 구현 사항

  • axiosClient로 TMDB /search/movie 연동, API 키는 .env(VITE_TMDB_API_KEY)로 관리, .gitignore에 등록
  • 검색 폼(제목 입력, 성인 콘텐츠 체크박스, 언어 select)을 form으로 묶어 제출 시에만 API 호출(타이핑 중엔 미호출)
  • Input, LanguageSelector, MovieCard에 React.memo, 검색/언어 변경 핸들러는 useCallback, 영화 리스트는 useMemo로 가공
  • 영화 카드 클릭 시 상세 모달(포스터, 평점, 개봉일, 인기도, 줄거리, IMDb 검색 버튼) 표시

📎 스크린샷

image image image

✅ 체크리스트

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

🤔 질문 사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 영화 검색 및 상세 정보 조회 기능 추가
    • 검색 필터링 기능 (언어, 성인 콘텐츠 포함 여부) 추가
    • React 성능 최적화 연습 콘텐츠 추가 (콜백, 메모이제이션 예제)
  • Chores

    • 프로젝트 기본 설정 및 빌드 도구 구성 완료
    • ESLint, TypeScript 설정 추가

@SJ01-max
SJ01-max requested a review from wantkdd June 16, 2026 06:23
@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

3개의 독립 React + TypeScript + Vite 프로젝트를 신규 추가했다. Mission01은 TMDB API를 활용한 영화 검색 SPA이고, Practice01은 useCallback 훅 효과를 시연하며, Practice02는 useMemo를 활용한 소수 계산 최적화를 시연한다.


Mission01: TMDB 영화 검색 앱

Layer / File(s) Summary
도메인 타입 및 상수 정의
src/types/movie.ts, src/constants/movie.ts, src/utils/movie.ts
MovieLanguage, MovieFilters, Movie, MovieResponse 타입과 언어 옵션 목록, TMDB 이미지 베이스 URL, getRatingColor 유틸 함수를 선언한다.
프로젝트 설정 파일
package.json, vite.config.ts, postcss.config.js, eslint.config.js, tsconfig*.json, index.html, .gitignore, README.md, src/index.css
axios, tailwindcss, Vite React 플러그인을 포함하는 Mission01 전용 빌드/린트/CSS 환경을 구성한다.
Axios 클라이언트 및 useFetch 훅
src/api/axiosClient.ts, src/hooks/useFetch.ts
TMDB v3 기본 URL과 VITE_TMDB_API_KEY를 포함하는 Axios 인스턴스를 생성하고, AbortController로 취소 가능한 /search/movie 비동기 요청을 useEffect로 관리하는 useFetch 훅을 구현한다.
공통 UI 컴포넌트
src/components/SelectBox.tsx, src/components/Input.tsx, src/components/LanguageSelector.tsx, src/components/MovieCard.tsx, src/components/MovieList.tsx, src/components/MovieFilter.tsx, src/components/MovieModal.tsx
제네릭 SelectBox, Input, LanguageSelector, 포스터/평점/메타 조건부 렌더링 MovieCard, 그리드 MovieList, 검색 폼 MovieFilter, 전체화면 오버레이 MovieModal을 각각 memo로 감싸 추가한다.
HomePage 페이지 및 앱 진입점
src/pages/HomePage.tsx, src/App.tsx, src/main.tsx
draftFilters/submittedFilters 분리, useCallback 핸들러, useMemo 결과 가공, MovieModal 조건부 렌더링을 갖춘 HomePage를 구현하고, App과 main.tsx로 앱을 부트스트랩한다.

Practice01: useCallback 훅 실습

Layer / File(s) Summary
프로젝트 설정
package.json, vite.config.ts, eslint.config.js, tsconfig*.json, index.html, .gitignore, README.md
Practice01 전용 Vite+React 빌드/린트 환경을 구성한다.
UseCallbackPage 및 memo 컴포넌트
src/UseCallbackPage.tsx, src/components/CountButton.tsx, src/components/TextInput.tsx, src/App.tsx, src/main.tsx, src/index.css, src/App.css
count 핸들러는 useCallback으로 메모이즈하고 text 핸들러는 일반 함수로 정의해 memo 컴포넌트(CountButton, TextInput)의 선택적 리렌더링을 시연한다.

Practice02: useMemo 훅 실습

Layer / File(s) Summary
프로젝트 설정
package.json, vite.config.ts, eslint.config.js, tsconfig*.json, index.html, .gitignore, README.md
Practice02 전용 Vite+React 빌드/린트 환경을 구성한다.
UseMemoPage, 소수 유틸, memo 컴포넌트
src/utils/math.ts, src/UseMemoPage.tsx, src/components/TextInput.tsx, src/App.tsx, src/main.tsx, src/index.css, src/App.css
isPrime과 에라토스테네스의 체 기반 findPrimeNumbers를 구현하고, UseMemoPage에서 useMemo로 소수 목록을 캐싱해 note 입력 변경 시 재계산이 발생하지 않음을 시연한다.

Sequence Diagram(s)

sequenceDiagram
  participant 사용자
  participant MovieFilter
  participant HomePage
  participant useFetch
  participant axiosClient
  participant TMDB_API
  participant MovieModal

  사용자->>MovieFilter: 검색어/언어/성인 여부 입력
  MovieFilter->>HomePage: onSubmit(draftFilters)
  HomePage->>HomePage: submittedFilters 갱신
  HomePage->>useFetch: submittedFilters 전달
  useFetch->>axiosClient: GET /search/movie (AbortController)
  axiosClient->>TMDB_API: api_key + 필터 파라미터
  TMDB_API-->>axiosClient: MovieResponse
  axiosClient-->>useFetch: data
  useFetch-->>HomePage: { data, isLoading, error }
  HomePage->>HomePage: useMemo로 results 가공
  사용자->>HomePage: MovieCard 클릭
  HomePage->>MovieModal: selectedMovie 전달
  사용자->>MovieModal: 닫기 또는 오버레이 클릭
  MovieModal->>HomePage: onClose 호출
  HomePage->>HomePage: selectedMovie 초기화
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes


Possibly related PRs

  • HSU-Makeus-Challenge-10th/Web#28: 동일하게 TMDB API를 호출하는 Axios 기반 useFetch 훅과 MovieCard 컴포넌트를 React 앱으로 구성하는 PR이다.

Poem

🐇 토끼가 코드를 심었어요,
영화 포스터 쭉 늘어서고 🎬
useMemo로 소수를 세고,
useCallback으로 렌더를 줄이고,
TMDB 바다를 헤엄쳐 🌊
오늘도 화면에 별이 떴어요 ⭐

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive PR 제목이 실제 변경 사항과 완전히 일치하지 않습니다. 실제로는 3개의 과제(practice01, practice02, Mission01)가 포함되어 있으나, 제목에서는 'Mission1, 2'라고 표기하여 혼동을 유발할 수 있습니다. PR 제목을 'Create Week10 Practice01, Practice02, Mission01'로 수정하거나 더 명확하게 작성해주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 필수 템플릿의 모든 주요 섹션을 포함하고 있으며, 각 미션별로 구현 사항을 상세히 기술했고 스크린샷도 충분히 첨부했습니다. 체크리스트도 모두 완료되었습니다.
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 SJ01-max/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.

@SJ01-max SJ01-max self-assigned this Jun 16, 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: 7

🧹 Nitpick comments (1)
Week10/SJ01-max/Mission01/src/components/MovieFilter.tsx (1)

29-46: ⚡ Quick win

폼 입력 필드에 명시적 레이블 연결 추가 권장

영화 제목 입력과 언어 선택기가 명시적인 htmlFor/id 연결이 없습니다. 접근성 향상을 위해 다음과 같이 개선하는 것이 좋습니다:

💡 제안된 레이블 연결 개선
-        <div className="flex-1">
-          <label className="mb-1 block text-sm font-semibold text-gray-700">
-            영화 제목
-          </label>
-          <Input value={query} onChange={onQueryChange} />
-        </div>
+        <div className="flex-1">
+          <label htmlFor="movie-query" className="mb-1 block text-sm font-semibold text-gray-700">
+            영화 제목
+          </label>
+          <Input id="movie-query" value={query} onChange={onQueryChange} />
+        </div>

Input 컴포넌트에 id prop을 추가로 지원하도록 수정하면 됩니다.

interface InputProps {
+  id?: string;
   value: string;
   onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

function Input({ id, value, onChange }: InputProps) {
   return (
     <input
+      id={id}
       value={value}
       onChange={onChange}
       placeholder="영화 제목을 입력하세요"
       className="w-full rounded border border-gray-300 px-3 py-2 text-sm"
     />
   );
}
🤖 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/SJ01-max/Mission01/src/components/MovieFilter.tsx` around lines 29 -
46, The form inputs in MovieFilter.tsx lack explicit label connections for
accessibility. Modify the Input component to accept and render an id prop. Then
in MovieFilter.tsx, add an id prop to the Input component used for the movie
title query field, add a corresponding htmlFor attribute to the label wrapping
"영화 제목", and add an id to the checkbox input with a corresponding htmlFor on its
surrounding label to ensure all form controls are properly associated with their
labels for screen reader users.
🤖 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/SJ01-max/Mission01/src/api/axiosClient.ts`:
- Around line 3-11: The axiosClient creation does not validate that the
VITE_TMDB_API_KEY environment variable is defined before using it. Add a
validation check immediately after retrieving import.meta.env.VITE_TMDB_API_KEY
to ensure it exists; if it is undefined or empty, throw a descriptive error that
clearly indicates the missing environment variable. This prevents silent API
failures and makes debugging easier by providing an explicit error message at
initialization time rather than when requests fail.

In `@Week10/SJ01-max/Mission01/src/components/MovieCard.tsx`:
- Around line 15-18: The interactive card element in MovieCard.tsx uses a div
with an onClick handler, which is not keyboard-accessible and prevents users
from navigating and interacting with it via keyboard. Replace the div element
with a button element to enable keyboard navigation and selection, keeping the
onClick handler with onSelect(movie) and the className intact. This semantic
change ensures the card selection is accessible to all users including those
relying on keyboard or assistive technologies.

In `@Week10/SJ01-max/Mission01/src/components/MovieModal.tsx`:
- Around line 16-23: Add accessibility semantics and keyboard support to the
MovieModal component. The inner modal content container (the div with
onClick={(e) => e.stopPropagation()} and the white background) needs two ARIA
attributes: role="dialog" and aria-modal="true" to properly communicate modal
state to screen readers. Additionally, implement ESC key handling by adding a
useEffect hook that listens for the Escape key and calls the onClose callback
when pressed, ensuring keyboard users can close the modal. The ESC handler
should have proper cleanup to remove the event listener when the component
unmounts or when onClose changes.

In `@Week10/SJ01-max/Mission01/src/hooks/useFetch.ts`:
- Around line 17-21: When the query filter is empty in the useFetch hook
useEffect, the data state is reset to null but the error state from previous
requests is not cleared, causing stale error messages to remain visible. Add a
call to reset the error state (likely setError(null)) alongside setData(null) in
the condition that checks if filters.query is empty to ensure both data and
error states are properly initialized when the user clears their search query.

In `@Week10/SJ01-max/Practice01/src/index.css`:
- Around line 14-16: The `value-keyword-case` stylelint rule is being violated
in the CSS custom properties --sans, --heading, and --mono definitions (and
another occurrence at line 24). Convert all mixed-case font family names to
lowercase to comply with the rule: change `Roboto` to `roboto` and `Consolas` to
`consolas` in the respective font-family declarations. Ensure all keyword values
in the CSS use consistent lowercase formatting to pass the stylelint validation
gate.

In `@Week10/SJ01-max/Practice02/src/index.css`:
- Around line 14-16: Fix the stylelint `value-keyword-case` rule violations in
the CSS custom properties for font families. Review and adjust the casing of the
font family values in the CSS variables defined for --sans, --heading, and
--mono (at lines 14-16 and line 24) to comply with stylelint's case
requirements. Ensure all CSS keyword values follow consistent lowercase
formatting as expected by the configured stylelint rules to prevent lint
failures.

In `@Week10/SJ01-max/Practice02/src/utils/math.ts`:
- Around line 9-19: The findPrimeNumbers function does not validate the max
parameter, causing a RangeError when max is non-integer (like 10.5), Infinity,
or NaN when passed to new Array(max + 1). Before creating the sieve array,
normalize max by converting it to a finite integer using Math.floor() and add a
check to ensure max is a valid number. If max becomes invalid after
normalization, return an empty array just like the existing check for max < 2.

---

Nitpick comments:
In `@Week10/SJ01-max/Mission01/src/components/MovieFilter.tsx`:
- Around line 29-46: The form inputs in MovieFilter.tsx lack explicit label
connections for accessibility. Modify the Input component to accept and render
an id prop. Then in MovieFilter.tsx, add an id prop to the Input component used
for the movie title query field, add a corresponding htmlFor attribute to the
label wrapping "영화 제목", and add an id to the checkbox input with a corresponding
htmlFor on its surrounding label to ensure all form controls are properly
associated with their labels for screen reader users.
🪄 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: 4cecdc3b-d1ff-4165-9263-c59b44bfd440

📥 Commits

Reviewing files that changed from the base of the PR and between ae99498 and efa580b.

⛔ Files ignored due to path filters (15)
  • Week10/SJ01-max/Mission01/package-lock.json is excluded by !**/package-lock.json
  • Week10/SJ01-max/Mission01/public/favicon.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Mission01/public/icons.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice01/package-lock.json is excluded by !**/package-lock.json
  • Week10/SJ01-max/Practice01/public/favicon.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice01/public/icons.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice01/src/assets/hero.png is excluded by !**/*.png
  • Week10/SJ01-max/Practice01/src/assets/react.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice01/src/assets/vite.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice02/package-lock.json is excluded by !**/package-lock.json
  • Week10/SJ01-max/Practice02/public/favicon.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice02/public/icons.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice02/src/assets/hero.png is excluded by !**/*.png
  • Week10/SJ01-max/Practice02/src/assets/react.svg is excluded by !**/*.svg
  • Week10/SJ01-max/Practice02/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (58)
  • Week10/SJ01-max/Mission01/.gitignore
  • Week10/SJ01-max/Mission01/README.md
  • Week10/SJ01-max/Mission01/eslint.config.js
  • Week10/SJ01-max/Mission01/index.html
  • Week10/SJ01-max/Mission01/package.json
  • Week10/SJ01-max/Mission01/postcss.config.js
  • Week10/SJ01-max/Mission01/src/App.tsx
  • Week10/SJ01-max/Mission01/src/api/axiosClient.ts
  • Week10/SJ01-max/Mission01/src/components/Input.tsx
  • Week10/SJ01-max/Mission01/src/components/LanguageSelector.tsx
  • Week10/SJ01-max/Mission01/src/components/MovieCard.tsx
  • Week10/SJ01-max/Mission01/src/components/MovieFilter.tsx
  • Week10/SJ01-max/Mission01/src/components/MovieList.tsx
  • Week10/SJ01-max/Mission01/src/components/MovieModal.tsx
  • Week10/SJ01-max/Mission01/src/components/SelectBox.tsx
  • Week10/SJ01-max/Mission01/src/constants/movie.ts
  • Week10/SJ01-max/Mission01/src/hooks/useFetch.ts
  • Week10/SJ01-max/Mission01/src/index.css
  • Week10/SJ01-max/Mission01/src/main.tsx
  • Week10/SJ01-max/Mission01/src/pages/HomePage.tsx
  • Week10/SJ01-max/Mission01/src/types/movie.ts
  • Week10/SJ01-max/Mission01/src/utils/movie.ts
  • Week10/SJ01-max/Mission01/tsconfig.app.json
  • Week10/SJ01-max/Mission01/tsconfig.json
  • Week10/SJ01-max/Mission01/tsconfig.node.json
  • Week10/SJ01-max/Mission01/vite.config.ts
  • Week10/SJ01-max/Practice01/.gitignore
  • Week10/SJ01-max/Practice01/README.md
  • Week10/SJ01-max/Practice01/eslint.config.js
  • Week10/SJ01-max/Practice01/index.html
  • Week10/SJ01-max/Practice01/package.json
  • Week10/SJ01-max/Practice01/src/App.css
  • Week10/SJ01-max/Practice01/src/App.tsx
  • Week10/SJ01-max/Practice01/src/UseCallbackPage.tsx
  • Week10/SJ01-max/Practice01/src/components/CountButton.tsx
  • Week10/SJ01-max/Practice01/src/components/TextInput.tsx
  • Week10/SJ01-max/Practice01/src/index.css
  • Week10/SJ01-max/Practice01/src/main.tsx
  • Week10/SJ01-max/Practice01/tsconfig.app.json
  • Week10/SJ01-max/Practice01/tsconfig.json
  • Week10/SJ01-max/Practice01/tsconfig.node.json
  • Week10/SJ01-max/Practice01/vite.config.ts
  • Week10/SJ01-max/Practice02/.gitignore
  • Week10/SJ01-max/Practice02/README.md
  • Week10/SJ01-max/Practice02/eslint.config.js
  • Week10/SJ01-max/Practice02/index.html
  • Week10/SJ01-max/Practice02/package.json
  • Week10/SJ01-max/Practice02/src/App.css
  • Week10/SJ01-max/Practice02/src/App.tsx
  • Week10/SJ01-max/Practice02/src/UseMemoPage.tsx
  • Week10/SJ01-max/Practice02/src/components/TextInput.tsx
  • Week10/SJ01-max/Practice02/src/index.css
  • Week10/SJ01-max/Practice02/src/main.tsx
  • Week10/SJ01-max/Practice02/src/utils/math.ts
  • Week10/SJ01-max/Practice02/tsconfig.app.json
  • Week10/SJ01-max/Practice02/tsconfig.json
  • Week10/SJ01-max/Practice02/tsconfig.node.json
  • Week10/SJ01-max/Practice02/vite.config.ts

Comment on lines +3 to +11
const axiosClient = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: import.meta.env.VITE_TMDB_API_KEY,
},
headers: {
accept: "application/json",
},
});

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

환경 변수 누락 시 런타임 검증 필요

VITE_TMDB_API_KEY가 정의되지 않았을 경우 undefined가 쿼리 파라미터로 전달되어 모든 API 요청이 실패하지만, 명확한 에러 메시지 없이 조용히 실패합니다.

🛡️ 제안: 초기화 시점에 API 키 존재 여부 검증
 import axios from "axios";

+const apiKey = import.meta.env.VITE_TMDB_API_KEY;
+
+if (!apiKey) {
+  throw new Error("VITE_TMDB_API_KEY 환경 변수가 설정되지 않았습니다.");
+}
+
 const axiosClient = axios.create({
   baseURL: "https://api.themoviedb.org/3",
   params: {
-    api_key: import.meta.env.VITE_TMDB_API_KEY,
+    api_key: apiKey,
   },
   headers: {
     accept: "application/json",
   },
 });
📝 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 axiosClient = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: import.meta.env.VITE_TMDB_API_KEY,
},
headers: {
accept: "application/json",
},
});
import axios from "axios";
const apiKey = import.meta.env.VITE_TMDB_API_KEY;
if (!apiKey) {
throw new Error("VITE_TMDB_API_KEY 환경 변수가 설정되지 않았습니다.");
}
const axiosClient = axios.create({
baseURL: "https://api.themoviedb.org/3",
params: {
api_key: apiKey,
},
headers: {
accept: "application/json",
},
});
🤖 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/SJ01-max/Mission01/src/api/axiosClient.ts` around lines 3 - 11, The
axiosClient creation does not validate that the VITE_TMDB_API_KEY environment
variable is defined before using it. Add a validation check immediately after
retrieving import.meta.env.VITE_TMDB_API_KEY to ensure it exists; if it is
undefined or empty, throw a descriptive error that clearly indicates the missing
environment variable. This prevents silent API failures and makes debugging
easier by providing an explicit error message at initialization time rather than
when requests fail.

Comment on lines +15 to +18
<div
onClick={() => onSelect(movie)}
className="cursor-pointer overflow-hidden rounded-lg bg-white shadow"
>

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

카드 선택 동작을 키보드로도 수행 가능하게 바꿔주세요.

현재 클릭 가능한 요소가 div라서 키보드 탐색/실행이 막혀 모달 진입이 어려워집니다. 상호작용 루트를 button으로 바꾸는 쪽이 안전합니다.

접근성 보완 예시 diff
-    <div
-      onClick={() => onSelect(movie)}
-      className="cursor-pointer overflow-hidden rounded-lg bg-white shadow"
-    >
+    <button
+      type="button"
+      onClick={() => onSelect(movie)}
+      className="w-full cursor-pointer overflow-hidden rounded-lg bg-white text-left shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
+    >
@@
-    </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
onClick={() => onSelect(movie)}
className="cursor-pointer overflow-hidden rounded-lg bg-white shadow"
>
<button
type="button"
onClick={() => onSelect(movie)}
className="w-full cursor-pointer overflow-hidden rounded-lg bg-white text-left shadow focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500"
>
🤖 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/SJ01-max/Mission01/src/components/MovieCard.tsx` around lines 15 - 18,
The interactive card element in MovieCard.tsx uses a div with an onClick
handler, which is not keyboard-accessible and prevents users from navigating and
interacting with it via keyboard. Replace the div element with a button element
to enable keyboard navigation and selection, keeping the onClick handler with
onSelect(movie) and the className intact. This semantic change ensures the card
selection is accessible to all users including those relying on keyboard or
assistive technologies.

Comment on lines +16 to +23
<div
onClick={onClose}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
>
<div
onClick={(e) => e.stopPropagation()}
className="max-h-full w-full max-w-2xl overflow-y-auto rounded-lg bg-white"
>

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 16-23 구간 모달 컨테이너에 role="dialog"/aria-modal="true"가 없고 ESC 키 닫기 핸들링도 없어, 스크린리더·키보드 사용자에게 모달 상태가 명확히 전달되지 않습니다.

수정 예시
-    <div
-      onClick={onClose}
-      className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
-    >
+    <div
+      onClick={onClose}
+      onKeyDown={(e) => {
+        if (e.key === "Escape") onClose();
+      }}
+      role="dialog"
+      aria-modal="true"
+      aria-label={`${movie.title} 상세 정보`}
+      tabIndex={-1}
+      className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
+    >
🤖 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/SJ01-max/Mission01/src/components/MovieModal.tsx` around lines 16 -
23, Add accessibility semantics and keyboard support to the MovieModal
component. The inner modal content container (the div with onClick={(e) =>
e.stopPropagation()} and the white background) needs two ARIA attributes:
role="dialog" and aria-modal="true" to properly communicate modal state to
screen readers. Additionally, implement ESC key handling by adding a useEffect
hook that listens for the Escape key and calls the onClose callback when
pressed, ensuring keyboard users can close the modal. The ESC handler should
have proper cleanup to remove the event listener when the component unmounts or
when onClose changes.

Comment on lines +17 to +21
useEffect(() => {
if (!filters.query) {
setData(null);
return;
}

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

쿼리가 비었을 때 에러 상태도 초기화 필요

query가 비어 있으면 data를 null로 초기화하지만, 이전 요청에서 발생한 error 상태는 그대로 유지됩니다. 사용자가 검색어를 지운 후에도 이전 에러 메시지가 화면에 남게 됩니다.

🐛 제안: 에러 상태도 함께 초기화
   useEffect(() => {
     if (!filters.query) {
       setData(null);
+      setError(null);
       return;
     }
🤖 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/SJ01-max/Mission01/src/hooks/useFetch.ts` around lines 17 - 21, When
the query filter is empty in the useFetch hook useEffect, the data state is
reset to null but the error state from previous requests is not cleared, causing
stale error messages to remain visible. Add a call to reset the error state
(likely setError(null)) alongside setData(null) in the condition that checks if
filters.query is empty to ensure both data and error states are properly
initialized when the user clears their search query.

Comment on lines +14 to +16
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace;

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

Stylelint 규칙 위반으로 lint 단계가 깨질 수 있습니다.

Line 14, Line 15, Line 16, Line 24의 값 대소문자가 현재 value-keyword-case 규칙과 충돌합니다. 린트 게이트를 사용 중이면 이 PR이 차단될 수 있습니다.

수정 예시 diff
-  --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
-  --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
-  --mono: ui-monospace, Consolas, monospace;
+  --sans: system-ui, 'Segoe UI', roboto, sans-serif;
+  --heading: system-ui, 'Segoe UI', roboto, sans-serif;
+  --mono: ui-monospace, consolas, monospace;
@@
-  text-rendering: optimizeLegibility;
+  text-rendering: optimizelegibility;

Also applies to: 24-24

🧰 Tools
🪛 Stylelint (17.13.0)

[error] 14-14: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)


[error] 15-15: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)


[error] 16-16: Expected "Consolas" to be "consolas" (value-keyword-case)

(value-keyword-case)

🤖 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/SJ01-max/Practice01/src/index.css` around lines 14 - 16, The
`value-keyword-case` stylelint rule is being violated in the CSS custom
properties --sans, --heading, and --mono definitions (and another occurrence at
line 24). Convert all mixed-case font family names to lowercase to comply with
the rule: change `Roboto` to `roboto` and `Consolas` to `consolas` in the
respective font-family declarations. Ensure all keyword values in the CSS use
consistent lowercase formatting to pass the stylelint validation gate.

Source: Linters/SAST tools

Comment on lines +14 to +16
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace;

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

Stylelint 오류를 정리해 린트 실패 가능성을 제거하세요.

Line 14, Line 15, Line 16, Line 24에서 value-keyword-case 규칙 위반이 보고됩니다. 현재 설정에서 스타일 린트를 돌리면 실패할 수 있으니 이 PR에서 같이 맞추는 게 안전합니다.

🔧 제안 수정안
-  --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
-  --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
-  --mono: ui-monospace, Consolas, monospace;
+  --sans: system-ui, 'Segoe UI', roboto, sans-serif;
+  --heading: system-ui, 'Segoe UI', roboto, sans-serif;
+  --mono: ui-monospace, consolas, monospace;

-  text-rendering: optimizeLegibility;
+  text-rendering: optimizelegibility;

Also applies to: 24-24

🧰 Tools
🪛 Stylelint (17.13.0)

[error] 14-14: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)


[error] 15-15: Expected "Roboto" to be "roboto" (value-keyword-case)

(value-keyword-case)


[error] 16-16: Expected "Consolas" to be "consolas" (value-keyword-case)

(value-keyword-case)

🤖 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/SJ01-max/Practice02/src/index.css` around lines 14 - 16, Fix the
stylelint `value-keyword-case` rule violations in the CSS custom properties for
font families. Review and adjust the casing of the font family values in the CSS
variables defined for --sans, --heading, and --mono (at lines 14-16 and line 24)
to comply with stylelint's case requirements. Ensure all CSS keyword values
follow consistent lowercase formatting as expected by the configured stylelint
rules to prevent lint failures.

Source: Linters/SAST tools

Comment on lines +9 to +19
export function findPrimeNumbers(max: number): (number | null)[] {
if (max < 2) return [];

const sieve = new Array<boolean>(max + 1).fill(true);
sieve[0] = false;
sieve[1] = false;

for (let i = 2; i * i <= max; i++) {
if (sieve[i]) {
for (let j = i * i; j <= max; j += i) {
sieve[j] = 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 | 🔴 Critical | ⚡ Quick win

비정수/무한대 입력 시 배열 길이 예외로 크래시가 발생합니다.

Line 12의 new Array(max + 1)은 max가 10.5, Infinity, NaN이면 RangeError를 유발합니다. max를 유한한 정수로 정규화한 뒤 sieve를 생성해야 합니다.

수정 예시
 export function findPrimeNumbers(max: number): (number | null)[] {
-  if (max < 2) return [];
+  if (!Number.isFinite(max)) return [];
+  const safeMax = Math.floor(max);
+  if (safeMax < 2) return [];

-  const sieve = new Array<boolean>(max + 1).fill(true);
+  const sieve = new Array<boolean>(safeMax + 1).fill(true);
   sieve[0] = false;
   sieve[1] = false;

-  for (let i = 2; i * i <= max; i++) {
+  for (let i = 2; i * i <= safeMax; i++) {
     if (sieve[i]) {
-      for (let j = i * i; j <= max; j += i) {
+      for (let j = i * i; j <= safeMax; j += i) {
         sieve[j] = false;
       }
     }
   }
🤖 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/SJ01-max/Practice02/src/utils/math.ts` around lines 9 - 19, The
findPrimeNumbers function does not validate the max parameter, causing a
RangeError when max is non-integer (like 10.5), Infinity, or NaN when passed to
new Array(max + 1). Before creating the sieve array, normalize max by converting
it to a finite integer using Math.floor() and add a check to ensure max is a
valid number. If max becomes invalid after normalization, return an empty array
just like the existing check for max < 2.

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