Conversation
📝 WalkthroughWalkthrough두 개의 새 React+TypeScript+Vite 프로젝트(Week04/chulee-53/mission01, mission02-03)를 추가합니다. Mission01은 TMDB 기반 영화 탐색 앱, Mission02-03은 인증·폼(react-hook-form + Zod) 기능을 포함한 앱으로 프로젝트 설정, 라우팅, 컴포넌트, 훅, 타입 및 빌드 구성이 추가되었습니다. Changes
Sequence Diagram(s)Mission01: 영화 앱 데이터 흐름sequenceDiagram
participant User as 사용자
participant Router as 라우터
participant Page as MoviePage/MovieDetailPage
participant Hook as useCustomFetch
participant TMDB as The Movie DB API
User->>Router: 카테고리/영화 클릭
Router->>Page: 해당 페이지 렌더링
Page->>Hook: GET /movie(s)... (Bearer token)
Hook->>TMDB: 요청 (Authorization: Bearer ...)
TMDB-->>Hook: JSON 응답 (movies / movie+credits)
Hook-->>Page: {data, pending, error}
Page->>Page: 컴포넌트 렌더링 (MovieCard, MovieBanner, PersonCard)
Mission02-03: 인증 및 폼 검증 흐름sequenceDiagram
participant User as 사용자
participant Form as SignupPage/LoginPage
participant Validate as Zod/validate.ts
participant API as src/api/auth.ts
participant Server as 백엔드
participant Storage as LocalStorage
User->>Form: 입력(이메일, 비밀번호 등)
Form->>Validate: 입력 검증 (Zod / validate.ts)
Validate-->>Form: errors / valid
User->>Form: 제출
Form->>API: signup()/login()
API->>Server: POST /v1/auth/...
Server-->>API: 토큰 및 사용자 응답
API-->>Form: 응답 데이터
Form->>Storage: setItem(accessToken)
Form->>Form: navigate('/mypage')
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
시 🐰
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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: 17
🧹 Nitpick comments (14)
Week04/chulee-53/mission02-03/src/components/Input.tsx (1)
11-22: 에러 메시지를 입력 필드와 ARIA로 연결해 주세요.현재는 시각적으로만 에러가 표시되어 보조기기에서 오류 맥락 전달이 약합니다.
aria-invalid,aria-describedby, 에러id를 연결하면 접근성이 좋아집니다.접근성 개선 예시
-const Input = ({ errorMessage, className, ...props }: InputProps) => { +const Input = ({ errorMessage, className, id, ...props }: InputProps) => { + const errorId = id && errorMessage ? `${id}-error` : undefined; return ( <div className="flex flex-col gap-2"> <input + id={id} + aria-invalid={!!errorMessage} + aria-describedby={errorId} className={cn("bg-transparent text-white text-sm px-4 py-3 rounded-md outline-none border transition-colors", errorMessage ? 'border-red-500 focus:border-blue-500' : 'border-gray-500 hover:border-gray-400 focus:border-blue-500', className || '' )} {...props} /> - {errorMessage && <p className="text-red-500 text-xs">{errorMessage}</p>} + {errorMessage && <p id={errorId} className="text-red-500 text-xs">{errorMessage}</p>} </div> ); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/components/Input.tsx` around lines 11 - 22, The Input component should expose ARIA attributes when an error exists: if errorMessage is set, add aria-invalid="true" to the <input>, ensure the input has an id (use props.id if provided, or generate/derive one) and render the error <p> with a matching id (e.g., `${id}-error`), then set aria-describedby on the <input> to that error id so assistive tech can read the message; update the JSX in the Input function (which reads errorMessage, className, ...props) to wire id, aria-invalid and aria-describedby accordingly while preserving existing classes and spreading other props.Week04/chulee-53/mission02-03/src/vite-env.d.ts (1)
1-7: Vite 클라이언트 타입 참조 추가를 권장합니다.표준 Vite 타입을 상속받기 위해 파일 상단에
/// <reference types="vite/client" />참조를 추가하는 것이 좋습니다. 이렇게 하면 Vite의 기본 제공 타입과 함께 커스텀 환경 변수 타입을 확장할 수 있습니다.💡 권장 수정 사항
+/// <reference types="vite/client" /> + interface ImportMetaEnv { readonly VITE_SERVER_API_URL: string; } interface ImportMeta { readonly env: ImportMetaEnv; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/vite-env.d.ts` around lines 1 - 7, Add the Vite client type reference at the top of the file so your custom ImportMetaEnv and ImportMeta extend Vite's built-in types: insert the triple-slash reference directive for "vite/client" above the existing declarations (so the file begins with /// <reference types="vite/client" />), then keep the current ImportMetaEnv and ImportMeta interfaces (VITE_SERVER_API_URL etc.) as-is to augment the Vite types.Week04/chulee-53/mission01/src/App.tsx (1)
8-19: 상대 경로 패턴 사용을 고려하세요.현재 코드는 정상적으로 작동합니다.
HomePage컴포넌트가<Outlet />을 렌더링하고 있으므로 중첩된 라우트가 제대로 표시됩니다. 다만, 자식 라우트에서path: '/movies/:category'대신path: 'movies/:category'처럼 상대 경로를 사용하는 것이 React Router v6의 권장 패턴입니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission01/src/App.tsx` around lines 8 - 19, The child route in the router definition uses an absolute path '/movies/:category' which prevents proper relative nesting; update the child route inside createBrowserRouter so its path is relative ('movies/:category') to HomePage (which renders an <Outlet />), keeping element: <MoviePage /> and errorElement: <NotFound /> unchanged; locate the route object in the array passed to createBrowserRouter and replace the leading-slash path with the relative path.Week04/chulee-53/mission01/src/pages/MoviePage.tsx (1)
41-41: 페이지 라벨이 영어와 한국어가 혼합되어 있습니다.
{page}page대신 한국어 컨텍스트에 맞게{page}페이지또는{page} / {totalPages}로 표시하는 것이 일관성 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission01/src/pages/MoviePage.tsx` at line 41, The span currently renders mixed-language label "{page}page"; update the JSX in MoviePage.tsx (the span with className "bg-[`#F3F4F4`] text-black px-6 py-2 rounded-lg border-[`#F8F3E1`] border-b-4") to use a Korean-consistent label such as "{page}페이지" or a clearer paginator like "{page} / {totalPages}" by replacing the "{page}page" expression accordingly and ensuring totalPages is available if you choose the latter.Week04/chulee-53/mission02-03/src/App.tsx (1)
19-19:/mypage라우트가 인증 없이 접근 가능합니다.현재
/mypage라우트는 인증 여부와 관계없이 누구나 접근할 수 있습니다. 로그인하지 않은 사용자가 접근할 경우 로그인 페이지로 리다이렉트하는 보호 로직 추가를 고려해보세요.🛡️ ProtectedRoute 컴포넌트 예시
// components/ProtectedRoute.tsx import { Navigate, Outlet } from 'react-router-dom'; import { LOCAL_STORAGE_KEY } from '../constants/key'; export const ProtectedRoute = () => { const token = localStorage.getItem(LOCAL_STORAGE_KEY.accessToken); return token ? <Outlet /> : <Navigate to="/login" replace />; };라우터 설정에서 사용:
+import { ProtectedRoute } from './components/ProtectedRoute'; const router = createBrowserRouter([ { path: "/", element: <Layout />, errorElement: <div>Error</div>, children: [ { index: true, element: <HomePage /> }, { path: "login", element: <LoginPage /> }, { path: "signup", element: <SignupPage /> }, - { path: "mypage", element: <MyPage /> }, + { + element: <ProtectedRoute />, + children: [ + { path: "mypage", element: <MyPage /> }, + ], + }, ] }, ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/App.tsx` at line 19, The /mypage route is currently unprotected; add an authentication guard by creating or using a ProtectedRoute component and wrap or replace the MyPage route element with it (e.g., use ProtectedRoute that checks LOCAL_STORAGE_KEY.accessToken and redirects to "/login" when missing). Update the router entry that currently uses element: <MyPage /> to instead render via the ProtectedRoute (either as element: <ProtectedRoute><MyPage/></ProtectedRoute> or by using <Route path="mypage" element={<ProtectedRoute />} with an Outlet that renders MyPage). Ensure the guard reads the token from localStorage using LOCAL_STORAGE_KEY.accessToken and performs a Navigate to "/login" when no token is present.Week04/chulee-53/mission01/src/components/Navbar.tsx (1)
13-15: 클래스명 로직에 중복이 많습니다.active/inactive 상태의 스타일 대부분이 동일합니다. 공통 클래스를 추출하면 가독성과 유지보수성이 향상됩니다.
♻️ 리팩토링 제안
+const baseClasses = "cursor-pointer transition-all text-[`#F3F4F4`] px-6 py-2 rounded-xl border-b-4 hover:bg-[`#262624`] active:brightness-90 duration-300"; +const activeClasses = "bg-[`#262624`] border-[`#141413`]"; +const inactiveClasses = "bg-[`#141413`] border-[`#141413`]"; {LINKS.map(({ to, label }) => ( - <NavLink key={to} to={to} className={({ isActive }) => isActive ? "cursor-pointer transition-all bg-[`#262624`] text-[`#F3F4F4`] px-6 py-2 rounded-xl border-[`#141413`] border-b-4 hover:bg-[`#262624`] active:brightness-90 duration-300" : "cursor-pointer transition-all bg-[`#141413`] text-[`#F3F4F4`] px-6 py-2 rounded-xl border-[`#141413`] border-b-4 hover:bg-[`#262624`] active:brightness-90 duration-300"}>{label}</NavLink> + <NavLink + key={to} + to={to} + className={({ isActive }) => `${baseClasses} ${isActive ? activeClasses : inactiveClasses}`} + > + {label} + </NavLink> ))}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission01/src/components/Navbar.tsx` around lines 13 - 15, The NavLink className has a lot of duplicated classes inside the JSX mapping over LINKS; extract the shared classes into a common string and only vary the differing fragment based on isActive inside the className callback for NavLink in the component that maps LINKS; specifically, create a const like baseClasses (used by the mapping for LINKS) containing "cursor-pointer transition-all text-[`#F3F4F4`] px-6 py-2 rounded-xl border-[`#141413`] border-b-4 hover:bg-[`#262624`] active:brightness-90 duration-300" and then append the conditional bg class (e.g., "bg-[`#262624`]" when isActive else "bg-[`#141413`]") in the NavLink className function to remove duplication and improve readability.Week04/chulee-53/mission01/package.json (1)
13-18: Tailwind 관련 패키지를devDependencies로 이동하는 것이 좋습니다.
@tailwindcss/vite와tailwindcss는 빌드 타임에만 사용되는 도구이므로devDependencies에 위치하는 것이 적절합니다. 프로덕션 번들에 포함되지 않지만,dependencies에 있으면 프로덕션 설치 시 불필요하게 설치됩니다.📦 제안된 수정
"dependencies": { - "@tailwindcss/vite": "^4.2.2", "axios": "^1.14.0", "react": "^19.2.4", "react-dom": "^19.2.4", - "react-router-dom": "^7.14.0", - "tailwindcss": "^4.2.2" + "react-router-dom": "^7.14.0" }, "devDependencies": { + "@tailwindcss/vite": "^4.2.2", + "tailwindcss": "^4.2.2", "@eslint/js": "^9.39.4",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission01/package.json` around lines 13 - 18, Move the Tailwind build tools out of runtime dependencies by removing "@tailwindcss/vite" and "tailwindcss" from the dependencies block and adding them (with the same versions) under devDependencies in package.json so they are only installed for development/build; after editing, run your package manager to update node_modules and the lockfile to reflect the change.Week04/chulee-53/mission02-03/package.json (2)
14-14:@tailwindcss/vite는 devDependencies로 이동 권장
@tailwindcss/vite는 빌드 시에만 사용되는 Vite 플러그인입니다.devDependencies에 배치하는 것이 더 적절합니다.♻️ 수정 제안
"dependencies": { "@hookform/resolvers": "^5.2.2", - "@tailwindcss/vite": "^4.2.2", "axios": "^1.14.0", ... }, "devDependencies": { + "@tailwindcss/vite": "^4.2.2", "@eslint/js": "^9.39.4", ... }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/package.json` at line 14, Move the "@tailwindcss/vite" dependency out of the production "dependencies" section into "devDependencies" in package.json since it is a Vite build plugin only needed during development/build; locate the entry for "@tailwindcss/vite" and remove it from dependencies and add the same version string under devDependencies so package managers install it as a dev-only dependency.
17-18:lucide패키지 제거 권장코드베이스에서
lucide패키지를 직접 사용하는 부분이 없습니다. 모든 아이콘 임포트는lucide-react에서만 이루어지고 있으므로, package.json에서lucide를 제거하여 불필요한 종속성을 정리할 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/package.json` around lines 17 - 18, 패키지 종속성 정리: package.json의 "lucide" 의존성을 제거하고 "lucide-react"만 유지하도록 수정하세요; 수정 후 npm install 또는 yarn install을 실행해 lockfile과 node_modules를 갱신하고, 코드에서 직접 "lucide"를 임포트하는 곳이 없는지(아이콘 임포트는 모두 "lucide-react"로 되어 있는지) 확인해 테스트를 실행하여 문제가 없는지 검증하세요.Week04/chulee-53/mission02-03/src/pages/LoginPage.tsx (1)
70-75: 이메일 입력 필드의type속성이메일 입력 필드의
type이"text"로 설정되어 있습니다.type="email"을 사용하면 모바일에서 적절한 키보드가 표시되고, 브라우저의 기본 이메일 형식 검증도 활용할 수 있습니다.♻️ 수정 제안
<Input {...getInputProps('email')} - type="text" + type="email" placeholder="이메일을 입력해주세요!" errorMessage={error?.email} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/pages/LoginPage.tsx` around lines 70 - 75, The email Input currently uses type="text"; update the Input props used in the LoginPage where getInputProps('email') is spread so the input uses type="email" instead of "text" (e.g., change the Input component instance that spreads getInputProps('email') to have type="email") to enable mobile email keyboard and browser email validation while keeping placeholder and errorMessage unchanged.Week04/chulee-53/mission02-03/src/pages/SignupPage.tsx (1)
102-117: 아바타 이미지 UI와 실제 데이터 수집 불일치Step 3에서 아바타 이미지 미리보기를 표시하지만, 실제로 사용자가 아바타를 업로드하거나 선택하는 기능이 없습니다.
RequestSignup타입에는avatar필드가 선택적으로 존재합니다(Week04/chulee-53/mission02-03/src/types/auth.ts:3-9참조). 현재 UI는 사용자에게 아바타 변경이 가능할 것처럼 보이지만 실제로는 기본 이미지만 표시됩니다.아바타 업로드 기능을 구현하거나, 현재 기능 범위 외라면 UI에서 아바타 미리보기를 제거하는 것을 고려해주세요. 아바타 업로드 기능 구현을 도와드릴까요?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/pages/SignupPage.tsx` around lines 102 - 117, The avatar preview in SignupPage (rendered when step === 3, using default_avatar) misleads users because there's no way to upload/select an avatar while RequestSignup includes an optional avatar field; either add an upload control bound to the form or remove the preview: to add, render a file input or avatar picker in the step 3 block, hook it into your form handling (register or setValue for the 'avatar' field on change, and ensure the image src updates to the chosen file/URL), and update the RequestSignup payload to include the avatar value; to remove, delete the preview markup (the rounded div and img using default_avatar) so UI matches the current data model.Week04/chulee-53/mission01/src/pages/MovieDetailPage.tsx (1)
43-50: Cast/Crew 리스트의 키로 고유 식별자 사용 권장현재
key={director-${idx}}및key={cast-${idx}}와 같이 인덱스를 키로 사용하고 있습니다. TMDB API 응답의 각 cast/crew 멤버는 고유한id또는credit_id필드를 포함하고 있습니다. 더 나은 React 재조정 성능을 위해 이러한 고유 식별자를 사용하는 것이 권장됩니다.이를 구현하려면 먼저
Cast및Crew인터페이스를 TMDB의id또는credit_id필드를 포함하도록 확장해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission01/src/pages/MovieDetailPage.tsx` around lines 43 - 50, The map keys for directors and cast currently use array indices (e.g., in directors.map and cast.map producing PersonCard with key={`director-${idx}`}` / `key={`cast-${idx}`}`) which can hurt React reconciliation; update the Cast and Crew interfaces to include TMDB's unique identifier (id or credit_id), then change the key to use that unique field (e.g., key={person.credit_id || person.id}) in the PersonCard render; ensure any data parsing that builds directors/cast preserves the credit_id/id fields so PersonCard receives them.Week04/chulee-53/mission02-03/src/api/auth.ts (1)
10-22: Axios 호출에 응답 제네릭을 명시해 타입 안전성을 실제로 적용해주세요.Line 11, Line 16, Line 21에서 제네릭 없이
axiosInstance를 호출하면data추론이 약해져서 API 스키마 변경 시 컴파일 타임 보호를 못 받습니다.제안 diff
export const signup = async (body: RequestSignup): Promise<ResponseSignup> => { - const { data } = await axiosInstance.post("/v1/auth/signup", body); + const { data } = await axiosInstance.post<ResponseSignup>("/v1/auth/signup", body); return data; }; export const login = async (body: RequestLogin): Promise<ResponseLogin> => { - const { data } = await axiosInstance.post("/v1/auth/signin", body); + const { data } = await axiosInstance.post<ResponseLogin>("/v1/auth/signin", body); return data; }; export const getMyInfo = async (): Promise<ResponseMyInfo> => { - const { data } = await axiosInstance.get("/v1/users/me"); + const { data } = await axiosInstance.get<ResponseMyInfo>("/v1/users/me"); return data; };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/api/auth.ts` around lines 10 - 22, The axios calls in signup, login, and getMyInfo lack response generics so `data` is weakly typed; update the three calls in functions signup, login, and getMyInfo to invoke axiosInstance with the appropriate response generic (e.g., use ResponseSignup for signup, ResponseLogin for login, ResponseMyInfo for getMyInfo) so the returned `data` is correctly typed—keep the existing destructuring and return value but add the generic parameter to axiosInstance.post/get calls.Week04/chulee-53/mission02-03/src/hooks/useForm.ts (1)
3-6: 현재 구현은 “제네릭 폼”처럼 보이지만 실제로는 문자열 필드 전용입니다.
value를 항상string으로 처리(Line 12, Line 23)하므로,T를 무제한 제네릭으로 열어두면 숫자/불리언 필드에서 타입 계약이 깨집니다. 제네릭에 문자열 레코드 제약을 두는 게 안전합니다.제안 diff
-interface UseFormProps<T> { +interface UseFormProps<T extends Record<string, string>> { initialValues: T; validate: (values: T) => Record<keyof T, string>; } -function useForm<T>({ initialValues, validate }: UseFormProps<T>) { +function useForm<T extends Record<string, string>>({ + initialValues, + validate, +}: UseFormProps<T>) {Also applies to: 12-23
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Week04/chulee-53/mission02-03/src/hooks/useForm.ts` around lines 3 - 6, The generic type T in UseFormProps is unconstrained but the hook treats all field values as strings, so constrain T to a string-valued record (e.g., change UseFormProps<T> to UseFormProps<T extends Record<string, string>>) and update any related signatures (validate: (values: T) => Record<keyof T, string>) and the useForm implementation (handlers that assume value is string on lines where value is read/assigned) to use that constrained T so the typings match the runtime assumption.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Week04/chulee-53/mission01/.gitignore`:
- Around line 10-14: 현재 .gitignore에 .env 계열 항목이 빠져 있어 환경변수 파일이 커밋될 수 있습니다;
.gitignore 파일의 기존 목록(node_modules, dist, dist-ssr 등) 아래에 .env 및 .env.*(예:
.env.local, .env.development 등)를 추가하여 모든 .env 파일을 무시하도록 수정하고, 변경 후 git 캐시에서 이미
추적 중인 .env 파일이 있다면 git rm --cached <파일>로 제거해 커밋되지 않게 처리하세요.
In `@Week04/chulee-53/mission01/index.html`:
- Line 7: The page title in the index.html's <title> tag ("mission01-02-03")
doesn't match this entry's scope; update the <title> element in index.html to
the correct, consistent value (e.g., "mission01" or "mission01 - [app name]") so
the browser tab clearly reflects this mission's page; verify the change in the
<title> tag in Week04/chulee-53/mission01/index.html and save.
In `@Week04/chulee-53/mission01/src/components/MovieBanner.tsx`:
- Around line 12-16: In MovieBanner (component MovieBanner.tsx) the img src
concatenates movie.backdrop_path without null-check, producing invalid URLs when
movie.backdrop_path === null; update the component to check movie.backdrop_path
and either: 1) render a fallback image URL (or CSS placeholder) when
backdrop_path is null, or 2) conditionally render the <img> only when
backdrop_path is non-null and use a placeholder element otherwise; ensure you
reference movie.backdrop_path in the conditional and keep alt as movie.title for
accessibility.
In `@Week04/chulee-53/mission01/src/components/MovieCard.tsx`:
- Around line 19-23: The img src construction in the MovieCard component uses
movie.poster_path which is typed as string | null, causing invalid URLs when
null; update MovieCard to handle movie.poster_path by conditionally using a
fallback image URL or rendering a placeholder when movie.poster_path is null
(e.g., set src to a default poster URL or an inline placeholder and keep alt
using movie.title), and ensure the className and transition effects remain
applied to the placeholder so layout and styling are preserved.
In `@Week04/chulee-53/mission01/src/components/PersonCard.tsx`:
- Around line 8-10: The default external fallback URL in PersonCard.tsx (the
imageUrl variable that uses profilePath) is fragile; change the fallback to a
bundled/static asset inside the app (e.g., import or reference a file from your
public/assets or src/assets) and set imageUrl = profilePath ?
`https://image.tmdb.org/t/p/w200${profilePath}` :
'<local-static-path-or-imported-name>'; ensure the static file exists in the app
assets and update any image components to use that local path or imported
identifier instead of the external themoviedb SVG.
In `@Week04/chulee-53/mission01/src/hooks/useCustomFetch.tsx`:
- Around line 9-35: The hook's useEffect (inside useCustomFetch) needs request
cancellation to avoid race conditions: create an AbortController at the start of
the effect, pass its signal to axios.get (e.g., axios.get<T>(url, { signal,
headers: {...} })), catch and ignore abort errors (or detect err.name ===
'CanceledError' / axios.isCancel/err.code === 'ERR_CANCELED') so you don't
setError/setData on aborted requests, and return a cleanup function that calls
controller.abort() to cancel the in-flight request when url changes or the
component unmounts.
In `@Week04/chulee-53/mission01/src/pages/MoviePage.tsx`:
- Line 43: The "Next" button allows advancing past the last page; update the
MovieResponse type to include total_pages (e.g., add total_pages: number to
MovieResponse in types/movie.ts) and then disable the button when page >=
movieResponse.total_pages (or the prop/state holding total_pages) by adding a
disabled condition to the button that references page and total_pages and
prevent calling setPage(page + 1) when at the last page; ensure the button's
disabled prop and styling reflect this state.
In `@Week04/chulee-53/mission01/src/types/movie.ts`:
- Around line 1-16: The Movie type's runtime field is declared required but TMDB
list endpoints used by MovieResponse.results don't include runtime; change the
Movie definition so runtime is optional (use runtime?: number) to match actual
API responses and the runtime checks in MovieBanner, ensuring Movie and
MovieResponse types remain consistent.
In `@Week04/chulee-53/mission02-03/.gitignore`:
- Around line 10-14: The .gitignore currently lists node_modules, dist, dist-ssr
and *.local but misses common env files, so explicitly add patterns to ignore
environment files (e.g., .env, .env.*, .env.local, .env.production) to prevent
committing secrets; update the .gitignore by appending those entries alongside
the existing patterns (node_modules, dist, dist-ssr, *.local) so files like .env
and .env.production are excluded.
In `@Week04/chulee-53/mission02-03/src/constants/key.ts`:
- Around line 1-4: The current LOCAL_STORAGE_KEY object exposes both accessToken
and refreshToken keys which encourages storing refresh tokens in localStorage;
remove the refreshToken key from LOCAL_STORAGE_KEY and update any code that
reads/writes LOCAL_STORAGE_KEY.refreshToken to instead handle refresh tokens via
HttpOnly/Secure cookies on the server side, keeping only accessToken (or
renaming it if you prefer) in LOCAL_STORAGE_KEY; update usages in functions like
token storage/getters and auth flows to stop persisting refreshToken in
client-side storage and rely on cookie-based refresh endpoints.
In `@Week04/chulee-53/mission02-03/src/hooks/useForm.ts`:
- Around line 13-16: The state update in handleChange uses the closed-over
values variable which can lead to lost updates on rapid input; change the
setValues call to the functional update form so it always receives the latest
state (call setValues(prev => ({ ...prev, [name]: value }))), referencing the
handleChange function, setValues and values identifiers when making the change.
In `@Week04/chulee-53/mission02-03/src/hooks/useLocalStorage.ts`:
- Around line 10-18: getItem currently returns null when the key is absent but
implicit undefined when an exception occurs; make its behavior consistent by
explicitly returning null (or another consistent sentinel) from the catch block
and still logging the error. Update the getItem function in useLocalStorage.ts
(the const getItem = () => { ... } block) to console.error the caught error and
then return null so callers can reliably distinguish a missing key vs other code
paths.
In `@Week04/chulee-53/mission02-03/src/pages/LoginPage.tsx`:
- Around line 28-29: The code assumes login() returns an Axios response, but
login() already returns response.data, so replace uses of
response.data.accessToken with response.accessToken; specifically update the
block where you call login(values) (see the const response = await login(values)
and setItem(...) calls) to pass response.accessToken into setItem, ensuring
you're reading the accessToken directly from the returned data object.
- Around line 31-33: The catch block in the LoginPage component's login
submission handler currently only does console.log(error); replace this with
user-facing feedback by capturing the error and updating component state (e.g.,
setError / setLoginError) or invoking a toast/alert utility so the UI shows a
clear message; then render that error state in the LoginPage JSX (near the form
submit button or email/password fields) to inform the user of login failure and
optionally include a friendly message based on error.message or status.
In `@Week04/chulee-53/mission02-03/src/pages/MyPage.tsx`:
- Around line 6-18: The component currently only uses data/setData and leaves
the UI stuck on "로딩 중..." when getMyInfo() fails; update MyPage to add separate
loading and error state variables (e.g., loading, error) and wrap the async call
inside useEffect in a try/catch/finally so failures set error and clear loading;
also add an isMounted (or aborted) guard inside useEffect and a cleanup function
to avoid calling setData/setLoading on an unmounted component; ensure you still
call getMyInfo() and setData(response) on success and set error message on catch
so the UI can render error instead of infinite loading.
In `@Week04/chulee-53/mission02-03/src/pages/SignupPage.tsx`:
- Around line 37-44: The onSubmit handler calls signup(rest) but does not handle
API failures and always calls navigate('/'), and it leaves a debug console.log;
wrap the async call in a try/catch inside onSubmit (function name onSubmit,
using signup and navigate) so that navigate('/') only runs on successful
response, log or surface the error in the catch (remove the
console.log(response) debug), and optionally set form/server error state or show
a user-facing message when signup throws.
In `@Week04/chulee-53/mission02-03/src/utils/validate.ts`:
- Around line 4-13: validate.ts's email and password validators currently return
"" for empty inputs, allowing blank or whitespace-only values to pass; update
the email and password functions to first trim the input and if it's empty
return a required-field error (e.g., "이메일을 입력해주세요." / "비밀번호를 입력해주세요."), then
proceed with existing format/length checks (email regex and password length) so
whitespace-only strings are rejected; update the functions named "email" and
"password" accordingly.
---
Nitpick comments:
In `@Week04/chulee-53/mission01/package.json`:
- Around line 13-18: Move the Tailwind build tools out of runtime dependencies
by removing "@tailwindcss/vite" and "tailwindcss" from the dependencies block
and adding them (with the same versions) under devDependencies in package.json
so they are only installed for development/build; after editing, run your
package manager to update node_modules and the lockfile to reflect the change.
In `@Week04/chulee-53/mission01/src/App.tsx`:
- Around line 8-19: The child route in the router definition uses an absolute
path '/movies/:category' which prevents proper relative nesting; update the
child route inside createBrowserRouter so its path is relative
('movies/:category') to HomePage (which renders an <Outlet />), keeping element:
<MoviePage /> and errorElement: <NotFound /> unchanged; locate the route object
in the array passed to createBrowserRouter and replace the leading-slash path
with the relative path.
In `@Week04/chulee-53/mission01/src/components/Navbar.tsx`:
- Around line 13-15: The NavLink className has a lot of duplicated classes
inside the JSX mapping over LINKS; extract the shared classes into a common
string and only vary the differing fragment based on isActive inside the
className callback for NavLink in the component that maps LINKS; specifically,
create a const like baseClasses (used by the mapping for LINKS) containing
"cursor-pointer transition-all text-[`#F3F4F4`] px-6 py-2 rounded-xl
border-[`#141413`] border-b-4 hover:bg-[`#262624`] active:brightness-90
duration-300" and then append the conditional bg class (e.g., "bg-[`#262624`]"
when isActive else "bg-[`#141413`]") in the NavLink className function to remove
duplication and improve readability.
In `@Week04/chulee-53/mission01/src/pages/MovieDetailPage.tsx`:
- Around line 43-50: The map keys for directors and cast currently use array
indices (e.g., in directors.map and cast.map producing PersonCard with
key={`director-${idx}`}` / `key={`cast-${idx}`}`) which can hurt React
reconciliation; update the Cast and Crew interfaces to include TMDB's unique
identifier (id or credit_id), then change the key to use that unique field
(e.g., key={person.credit_id || person.id}) in the PersonCard render; ensure any
data parsing that builds directors/cast preserves the credit_id/id fields so
PersonCard receives them.
In `@Week04/chulee-53/mission01/src/pages/MoviePage.tsx`:
- Line 41: The span currently renders mixed-language label "{page}page"; update
the JSX in MoviePage.tsx (the span with className "bg-[`#F3F4F4`] text-black px-6
py-2 rounded-lg border-[`#F8F3E1`] border-b-4") to use a Korean-consistent label
such as "{page}페이지" or a clearer paginator like "{page} / {totalPages}" by
replacing the "{page}page" expression accordingly and ensuring totalPages is
available if you choose the latter.
In `@Week04/chulee-53/mission02-03/package.json`:
- Line 14: Move the "@tailwindcss/vite" dependency out of the production
"dependencies" section into "devDependencies" in package.json since it is a Vite
build plugin only needed during development/build; locate the entry for
"@tailwindcss/vite" and remove it from dependencies and add the same version
string under devDependencies so package managers install it as a dev-only
dependency.
- Around line 17-18: 패키지 종속성 정리: package.json의 "lucide" 의존성을 제거하고
"lucide-react"만 유지하도록 수정하세요; 수정 후 npm install 또는 yarn install을 실행해 lockfile과
node_modules를 갱신하고, 코드에서 직접 "lucide"를 임포트하는 곳이 없는지(아이콘 임포트는 모두 "lucide-react"로
되어 있는지) 확인해 테스트를 실행하여 문제가 없는지 검증하세요.
In `@Week04/chulee-53/mission02-03/src/api/auth.ts`:
- Around line 10-22: The axios calls in signup, login, and getMyInfo lack
response generics so `data` is weakly typed; update the three calls in functions
signup, login, and getMyInfo to invoke axiosInstance with the appropriate
response generic (e.g., use ResponseSignup for signup, ResponseLogin for login,
ResponseMyInfo for getMyInfo) so the returned `data` is correctly typed—keep the
existing destructuring and return value but add the generic parameter to
axiosInstance.post/get calls.
In `@Week04/chulee-53/mission02-03/src/App.tsx`:
- Line 19: The /mypage route is currently unprotected; add an authentication
guard by creating or using a ProtectedRoute component and wrap or replace the
MyPage route element with it (e.g., use ProtectedRoute that checks
LOCAL_STORAGE_KEY.accessToken and redirects to "/login" when missing). Update
the router entry that currently uses element: <MyPage /> to instead render via
the ProtectedRoute (either as element:
<ProtectedRoute><MyPage/></ProtectedRoute> or by using <Route path="mypage"
element={<ProtectedRoute />} with an Outlet that renders MyPage). Ensure the
guard reads the token from localStorage using LOCAL_STORAGE_KEY.accessToken and
performs a Navigate to "/login" when no token is present.
In `@Week04/chulee-53/mission02-03/src/components/Input.tsx`:
- Around line 11-22: The Input component should expose ARIA attributes when an
error exists: if errorMessage is set, add aria-invalid="true" to the <input>,
ensure the input has an id (use props.id if provided, or generate/derive one)
and render the error <p> with a matching id (e.g., `${id}-error`), then set
aria-describedby on the <input> to that error id so assistive tech can read the
message; update the JSX in the Input function (which reads errorMessage,
className, ...props) to wire id, aria-invalid and aria-describedby accordingly
while preserving existing classes and spreading other props.
In `@Week04/chulee-53/mission02-03/src/hooks/useForm.ts`:
- Around line 3-6: The generic type T in UseFormProps is unconstrained but the
hook treats all field values as strings, so constrain T to a string-valued
record (e.g., change UseFormProps<T> to UseFormProps<T extends Record<string,
string>>) and update any related signatures (validate: (values: T) =>
Record<keyof T, string>) and the useForm implementation (handlers that assume
value is string on lines where value is read/assigned) to use that constrained T
so the typings match the runtime assumption.
In `@Week04/chulee-53/mission02-03/src/pages/LoginPage.tsx`:
- Around line 70-75: The email Input currently uses type="text"; update the
Input props used in the LoginPage where getInputProps('email') is spread so the
input uses type="email" instead of "text" (e.g., change the Input component
instance that spreads getInputProps('email') to have type="email") to enable
mobile email keyboard and browser email validation while keeping placeholder and
errorMessage unchanged.
In `@Week04/chulee-53/mission02-03/src/pages/SignupPage.tsx`:
- Around line 102-117: The avatar preview in SignupPage (rendered when step ===
3, using default_avatar) misleads users because there's no way to upload/select
an avatar while RequestSignup includes an optional avatar field; either add an
upload control bound to the form or remove the preview: to add, render a file
input or avatar picker in the step 3 block, hook it into your form handling
(register or setValue for the 'avatar' field on change, and ensure the image src
updates to the chosen file/URL), and update the RequestSignup payload to include
the avatar value; to remove, delete the preview markup (the rounded div and img
using default_avatar) so UI matches the current data model.
In `@Week04/chulee-53/mission02-03/src/vite-env.d.ts`:
- Around line 1-7: Add the Vite client type reference at the top of the file so
your custom ImportMetaEnv and ImportMeta extend Vite's built-in types: insert
the triple-slash reference directive for "vite/client" above the existing
declarations (so the file begins with /// <reference types="vite/client" />),
then keep the current ImportMetaEnv and ImportMeta interfaces
(VITE_SERVER_API_URL etc.) as-is to augment the Vite types.
🪄 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: 5e98d70a-4924-4162-81e7-d6c2cf0115c0
⛔ Files ignored due to path filters (4)
Week04/chulee-53/mission01/package-lock.jsonis excluded by!**/package-lock.jsonWeek04/chulee-53/mission02-03/package-lock.jsonis excluded by!**/package-lock.jsonWeek04/chulee-53/mission02-03/src/images/default_avatar.pngis excluded by!**/*.pngWeek04/chulee-53/mission02-03/src/images/google_logo.pngis excluded by!**/*.png
📒 Files selected for processing (53)
Week04/chulee-53/mission01/.gitignoreWeek04/chulee-53/mission01/README.mdWeek04/chulee-53/mission01/eslint.config.jsWeek04/chulee-53/mission01/index.htmlWeek04/chulee-53/mission01/package.jsonWeek04/chulee-53/mission01/src/App.cssWeek04/chulee-53/mission01/src/App.tsxWeek04/chulee-53/mission01/src/components/LoadingSpinner.tsxWeek04/chulee-53/mission01/src/components/MovieBanner.tsxWeek04/chulee-53/mission01/src/components/MovieCard.tsxWeek04/chulee-53/mission01/src/components/Navbar.tsxWeek04/chulee-53/mission01/src/components/PersonCard.tsxWeek04/chulee-53/mission01/src/hooks/useCustomFetch.tsxWeek04/chulee-53/mission01/src/index.cssWeek04/chulee-53/mission01/src/main.tsxWeek04/chulee-53/mission01/src/pages/HomePage.tsxWeek04/chulee-53/mission01/src/pages/MovieDetailPage.tsxWeek04/chulee-53/mission01/src/pages/MoviePage.tsxWeek04/chulee-53/mission01/src/pages/NotFound.tsxWeek04/chulee-53/mission01/src/types/movie.tsWeek04/chulee-53/mission01/tsconfig.app.jsonWeek04/chulee-53/mission01/tsconfig.jsonWeek04/chulee-53/mission01/tsconfig.node.jsonWeek04/chulee-53/mission01/vite.config.tsWeek04/chulee-53/mission02-03/.gitignoreWeek04/chulee-53/mission02-03/README.mdWeek04/chulee-53/mission02-03/eslint.config.jsWeek04/chulee-53/mission02-03/index.htmlWeek04/chulee-53/mission02-03/package.jsonWeek04/chulee-53/mission02-03/src/App.cssWeek04/chulee-53/mission02-03/src/App.tsxWeek04/chulee-53/mission02-03/src/api/auth.tsWeek04/chulee-53/mission02-03/src/api/axios.tsWeek04/chulee-53/mission02-03/src/components/Input.tsxWeek04/chulee-53/mission02-03/src/components/Navbar.tsxWeek04/chulee-53/mission02-03/src/constants/key.tsWeek04/chulee-53/mission02-03/src/hooks/useForm.tsWeek04/chulee-53/mission02-03/src/hooks/useLocalStorage.tsWeek04/chulee-53/mission02-03/src/index.cssWeek04/chulee-53/mission02-03/src/layouts/Layout.tsxWeek04/chulee-53/mission02-03/src/main.tsxWeek04/chulee-53/mission02-03/src/pages/HomePage.tsxWeek04/chulee-53/mission02-03/src/pages/LoginPage.tsxWeek04/chulee-53/mission02-03/src/pages/MyPage.tsxWeek04/chulee-53/mission02-03/src/pages/SignupPage.tsxWeek04/chulee-53/mission02-03/src/types/auth.tsWeek04/chulee-53/mission02-03/src/types/common.tsWeek04/chulee-53/mission02-03/src/utils/validate.tsWeek04/chulee-53/mission02-03/src/vite-env.d.tsWeek04/chulee-53/mission02-03/tsconfig.app.jsonWeek04/chulee-53/mission02-03/tsconfig.jsonWeek04/chulee-53/mission02-03/tsconfig.node.jsonWeek04/chulee-53/mission02-03/vite.config.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Week04/chulee-53/mission02-03/src/pages/SignupPage.tsx`:
- Around line 52-54: The back button with onClick={() => navigate(-1)} that
renders <ChevronLeft /> lacks an accessible name; add an aria-label (e.g.,
aria-label="뒤로가기" or aria-label="Go back") to the button element that contains
the ChevronLeft icon so screen readers can announce its purpose (update the
button where ChevronLeft is rendered).
- Around line 63-69: The Google login button in SignupPage.tsx is a dead CTA;
either wire it to a real handler or make it clearly disabled. Add an onClick
handler (e.g., handleGoogleLogin) in the SignupPage component that triggers the
OAuth flow (or at minimum logs/alerts and calls the real auth function later),
or if the feature isn’t ready change the <button> to disabled with
aria-disabled="true", remove the interactive classes (e.g., cursor-pointer,
hover:bg-gray-900) and update styling/text to indicate "Coming soon" so users
aren’t presented with a clickable-but-nonfunctional control.
- Around line 43-44: In SignupPage.tsx replace the raw console.error(error)
inside the catch block of the signup handler with a sanitized logging and safe
user-facing error: do not stringify or log the full error object (which may
contain request payloads); instead log a generic message plus a non-sensitive
token such as error.message or a sanitized error code, and set state to show a
generic user-facing message like "Signup failed, please try again" (update the
signup handler / catch block and any local setError or notification calls
accordingly).
🪄 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: 34c19a40-56da-4e32-a01d-cc851fc190ae
📒 Files selected for processing (5)
Week04/chulee-53/mission01/src/types/movie.tsWeek04/chulee-53/mission02-03/src/hooks/useForm.tsWeek04/chulee-53/mission02-03/src/hooks/useLocalStorage.tsWeek04/chulee-53/mission02-03/src/pages/LoginPage.tsxWeek04/chulee-53/mission02-03/src/pages/SignupPage.tsx
✅ Files skipped from review due to trivial changes (2)
- Week04/chulee-53/mission02-03/src/hooks/useLocalStorage.ts
- Week04/chulee-53/mission01/src/types/movie.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- Week04/chulee-53/mission02-03/src/pages/LoginPage.tsx
- Week04/chulee-53/mission02-03/src/hooks/useForm.ts
📝 미션 번호
4주차 Misson 1, 2, 3
📋 구현 사항
📎 스크린샷
mission01.mp4
mission02.mp4
mission03.mp4
✅ 체크리스트
🤔 질문 사항
Summary by CodeRabbit
New Features
Documentation