Skip to content

#62 chore: Zod 스키마 기반 API 응답 검증 및 타입 중복 제거#63

Open
suminb99 wants to merge 31 commits intodevelopfrom
chore/62-zod-schema
Open

#62 chore: Zod 스키마 기반 API 응답 검증 및 타입 중복 제거#63
suminb99 wants to merge 31 commits intodevelopfrom
chore/62-zod-schema

Conversation

@suminb99
Copy link
Contributor

⚙️ Related ISSUE Number

Related #62



📄 Work Description

  • API 응답 Zod 스키마 도입 및 타입 중복 제거



📷 Screenshot



💬 To Reviewers



🔗 Reference

@suminb99 suminb99 requested a review from JiiminHa February 27, 2026 16:53
@suminb99 suminb99 self-assigned this Feb 27, 2026
@suminb99 suminb99 added the 🧹 chore 프로젝트 설정 / 빌드 / 패키지 관리 label Feb 27, 2026
@suminb99 suminb99 linked an issue Feb 27, 2026 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
snow-code-client Error Error Feb 28, 2026 0:38am

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • 새 기능

    • 관리자용 과제 관리 페이지 추가 — 과제 조회·삭제·편집 네비게이션 제공
    • 과제 선택/목록 관련 컴포넌트·훅 추가 및 단원 선택 연동
    • 단원 폼 지속 저장용 상태(store) 추가
  • 개선 사항

    • API 응답 런타임 검증 도입으로 데이터 신뢰성 향상
    • 쿼리·뮤테이션 구성 중앙화로 일관된 데이터 갱신 처리
    • 단원 편집/생성 및 과제 선택 흐름 개선(폼 복원, 뒤로가기 복귀)
  • 스타일/UX

    • 버튼·리스트 등 마이너 스타일 및 상호작용 조정

Walkthrough

관리자용 과제 관리 페이지 추가 및 API/타입/쿼리 리팩터: Zod 스키마 기반 API 응답 검증 도입, React Query 쿼리·뮤테이션 옵션 중앙화, Zustand 세션 스토어 추가, 도메인 타입을 z.infer로 전환, 과제/단위/과정 관련 컴포넌트·페이지의 선택/삭제/편집 흐름 변경. (요약 50단어 이내)

Changes

Cohort / File(s) Summary
라우팅
src/App.tsx
관리자 경로 assignments/manage 등록, AssignmentManagePage 라우트 추가.
Assignment: API · Queries · Mutations · Model
src/entities/assignment/api/assignmentApi.ts, src/entities/assignment/api/assignmentQueries.ts, src/entities/assignment/api/assignmentMutations.ts, src/entities/assignment/model/schemas.ts, src/entities/assignment/model/types.ts
apiResponseSchema·Zod 적용으로 응답 파싱 표준화, getAllAssignments·getAssignmentsByCourse·deleteAssignment 추가, 쿼리/뮤테이션 옵션 중앙화, 타입을 z.infer로 전환.
Course: API · Queries · Mutations · Model · Index
src/entities/course/api/courseApi.ts, src/entities/course/api/courseQueries.ts, src/entities/course/api/courseMutations.ts, src/entities/course/model/schemas.ts, src/entities/course/model/types.ts, src/entities/course/index.ts
응답을 Zod로 검증하도록 변경, 기존 queryOptions 모듈 제거, queries/mutations 모듈 추가, 스키마 기반 타입 전환 및 index에서 일부 재내보내기 제거.
Unit: API · Queries · Mutations · Model · Store
src/entities/unit/api/unitApi.ts, src/entities/unit/api/unitMutations.ts, src/entities/unit/api/unitQueries.ts, src/entities/unit/model/types.ts, src/entities/unit/model/useUnitStore.ts, src/entities/unit/model/schemas.ts
Zod 응답 파싱 적용·deleteAssignmentFromUnit 추가, create/update/delete 반환 파싱 변경, unitFormSchema/unitSchema 추가·Unit 타입 추출, sessionStorage 기반 Zustand 폼 스토어 도입, createUnit 인자명(unit→unitForm) 변경.
Pages — Assignment Manage / Select
src/pages/manage-assignment/AssignmentManagePage.tsx, src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx, src/pages/select-assignment/AssignmentSelectPage.tsx, src/pages/select-assignment/ui/AssignmentListContainer.tsx
관리자 과제 관리 페이지 신규(목록·삭제·편집), AssignmentSelectPage를 React Query·useUnitStore 기반으로 전환, 선택 데이터 형식이 Assignment 객체로 변경, 리스트 컨테이너 onSelect 시그니처 변경.
Pages — Unit Editor
src/pages/unit-editor/UnitEditorPage.tsx, src/pages/unit-editor/ui/UnitForm.tsx, src/pages/unit-editor/ui/UnitList.tsx, src/pages/unit-editor/model/types.ts
activeMode 기반 상태 전환 도입, useUnitStore 통합으로 폼 지속성 추가, onCreateUnit 파라미터명 변경(unit→unitForm), Mode 타입에서 'idle' 제거.
Student 모델
src/entities/student/model/schemas.ts, src/entities/student/model/types.ts
학생 진행·단위 관련 Zod 스키마 추가 및 기존 타입을 z.infer로 전환.
Auth
src/entities/auth/api/authApi.ts, src/entities/auth/model/schemas.ts, src/entities/auth/model/useUserStore.ts
kakaoLogin 시그니처에 role·studentId 추가, kakaoLoginResponseSchema로 응답 파싱 변경, UserType import 경로 조정.
Shared 모델/유틸
src/shared/model/schemas.ts, src/shared/model/index.ts, src/shared/model/type.ts
apiResponseSchema·errorResponseSchema·enum 스키마 추가, ApiResponse/UserType 재내보내기 경로 조정.
UI 컴포넌트·스타일·타입 조정
src/shared/ui/Layout.tsx, src/shared/ui/button/Button.tsx, src/shared/ui/list-row/ListRow.tsx, src/shared/ui/list-row/list-row-styles.ts
Layout 클래스 변경, Button prop formIDform으로 이름 변경, ListRow prop 타입 ReactNode 정규화, ListRow 기본 스타일에서 cursor-pointer 제거.

src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts
과제 목록을 병합·중복 제거하는 훅 추가(전체·과정별 조회 조합).
대시보드 통합
src/pages/dashboard/Dashboard.tsx
기존 queryOptions 호출을 courseQueries/assignmentQueries로 전환하고 뮤테이션을 courseMutations로 변경하여 쿼리키 무효화 방식 업데이트.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Browser as Browser(UI)
    participant Page as AssignmentManagePage
    participant Q as ReactQuery/QueryClient
    participant API as assignmentApi
    participant Server as Backend
    rect rgba(0,128,255,0.5)
    User->>Browser: 삭제 버튼 클릭
    Browser->>Page: onDelete(id)
    Page->>Browser: confirm() (사용자 확인)
    end
    Page->>API: deleteAssignment(id)
    API->>Server: DELETE /assignments/{id}
    Server-->>API: 200 {success,response:"..."}
    API-->>Page: parsed response (apiResponseSchema)
    Page->>Q: invalidateQueries(assignmentQueries.getAllAssignments().queryKey)
    Page->>Q: invalidateQueries(assignmentQueries.getAssignmentsByCourse(courseId).queryKey)
    Q-->>Page: refetch triggered
    Page-->>Browser: alert("삭제 완료")
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • 2025-snowCode/snowCode-Client#43: assignment/course API 및 쿼리 리팩터와 직접적으로 겹치는 변경(스키마·쿼리 구조).
  • 2025-snowCode/snowCode-Client#51: unit 관련 API·페이지(UnitEditor 등) 변경과 중복될 가능성 있음.
  • 2025-snowCode/snowCode-Client#37: 과제 선택/관리 UI 및 라우팅 변경과 중복 영역 존재.

Suggested labels

🛠️ refactor

Suggested reviewers

  • JiiminHa

추가 제안(짧게): API 응답 파싱 범위가 넓어졌으니 주요 엔드포인트에 대해 Zod parse 성공/실패 케이스의 통합 테스트나 E2E를 권장합니다. 참고: https://zod.dev/, https://tanstack.com/query/latest/docs/react/overview

짧은 칭찬 — 큰 리팩터, 깔끔해요!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 이 PR의 주요 변경사항인 'Zod 스키마 기반 API 응답 검증 및 타입 중복 제거'를 명확하게 반영하고 있습니다.
Description check ✅ Passed PR 설명은 관련 이슈 #62와 작업 내용(API 응답 Zod 스키마 도입 및 타입 중복 제거)을 포함하고 있어 변경사항과 관련이 있습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/62-zod-schema

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/unit-editor/ui/UnitForm.tsx (1)

66-73: ⚠️ Potential issue | 🟠 Major

콜백이 동기 함수이므로 isSubmitting 보호가 조기에 해제되어 중복 제출 위험이 있습니다.

onCreateUnitonUpdateUnitvoid를 반환하므로, 현재의 제안된 수정안(async/await)은 타입 시스템상 불가능합니다. 대신 useMutationmutateAsync를 활용해야 합니다:

  1. UnitEditorPage.tsx의 콜백을 다음과 같이 변경:

    const onCreateUnit = (unitForm: TUnitFormSchema) => 
      addUnit({courseId, unitForm}); // mutate 사용 시 Promise 반환 불가

    const onCreateUnit = async (unitForm: TUnitFormSchema) => 
      await addUnit({courseId, unitForm}); // mutateAsync 사용 필요
  2. UnitFormProps 타입 정의를 업데이트:

    onCreateUnit: (unit: TUnitFormSchema) => Promise<void>;
    onUpdateUnit: (unitId: number, unit: TUnitFormSchema) => Promise<void>;
  3. UnitForm.tsxonSubmit을 async로 변경:

    const onSubmit = async (data: TUnitFormSchema) => {
      if (mode === 'editing' && unit) {
        await onUpdateUnit(unit.id, data);
        return;
      }
      await onCreateUnit(data);
    };

이렇게 하면 handleSubmit이 콜백의 Promise를 인식하여 mutation 완료까지 isSubmitting을 유지하게 됩니다. TanStack Query 공식 문서의 "Using Mutations" 섹션을 참고하세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/ui/UnitForm.tsx` around lines 66 - 73, The submit
handler releases isSubmitting too early because onCreateUnit/onUpdateUnit are
currently synchronous (void); update UnitEditorPage.tsx to call the TanStack
Query mutateAsync (e.g., await addUnit.mutateAsync({...}) / await
updateUnit.mutateAsync(...)) and change the exported UnitFormProps so
onCreateUnit: (unit: TUnitFormSchema) => Promise<void> and onUpdateUnit:
(unitId: number, unit: TUnitFormSchema) => Promise<void>; then make
UnitForm.tsx's onSubmit async and await the calls to onCreateUnit/onUpdateUnit
(refer to onSubmit, UnitFormProps, onCreateUnit/onUpdateUnit, and
addUnit/updateUnit mutateAsync) so handleSubmit sees the Promise and keeps
isSubmitting until mutation completes.
🧹 Nitpick comments (16)
src/entities/auth/api/authApi.ts (1)

3-3: 임포트 경로는 절대 경로(@/...)로 통일해 주세요

Line 3의 상대 경로(../model/schemas)는 현재 프로젝트 컨벤션과 어긋납니다. 작은 차이지만 리팩토링/이동 시 경로 안정성이 크게 달라집니다.

빠른 수정안
-import {kakaoLoginResponseSchema} from '../model/schemas';
+import {kakaoLoginResponseSchema} from '@/entities/auth/model/schemas';

참고 문서: https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases

As per coding guidelines 절대 경로 임포트(@/...) 사용.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/auth/api/authApi.ts` at line 3, The import of
kakaoLoginResponseSchema in authApi.ts uses a relative path; change the import
to use the project's absolute alias (e.g. replace "../model/schemas" with an
absolute import like "@/entities/auth/model/schemas") so the
kakaoLoginResponseSchema import follows the `@/`... convention used across the
codebase and remains stable during refactors.
src/pages/unit-editor/model/types.ts (1)

10-13: mode: Mode | null 도입에 맞춰 UnitFormProps를 판별 유니온으로 분리해 주세요.

현재 구조는 mode 값과 무관하게 unitIndex/핸들러 조합이 모두 허용되어, 런타임 분기 의존도가 커집니다. 모드별 props를 타입으로 강제하면 안전성이 더 올라갑니다.

🔧 제안 diff
-export interface UnitFormProps {
-  unit?: Unit;
-  unitIndex: number;
-  mode: Mode | null;
-  onCreateUnit: (unit: TUnitFormSchema) => void;
-  onUpdateUnit: (unitId: number, unit: TUnitFormSchema) => void;
-  onDeleteUnit: (unitId: number) => void;
-}
+type UnitFormBaseProps = {
+  onDeleteUnit: (unitId: number) => void;
+};
+
+type CreatingUnitFormProps = UnitFormBaseProps & {
+  mode: 'creating';
+  unitIndex: number;
+  unit?: never;
+  onCreateUnit: (unit: TUnitFormSchema) => void;
+  onUpdateUnit?: never;
+};
+
+type EditingUnitFormProps = UnitFormBaseProps & {
+  mode: 'editing';
+  unit: Unit;
+  unitIndex: number;
+  onUpdateUnit: (unitId: number, unit: TUnitFormSchema) => void;
+  onCreateUnit?: never;
+};
+
+type InactiveUnitFormProps = UnitFormBaseProps & {
+  mode: null;
+  unit?: never;
+  unitIndex?: never;
+  onCreateUnit?: never;
+  onUpdateUnit?: never;
+};
+
+export type UnitFormProps =
+  | CreatingUnitFormProps
+  | EditingUnitFormProps
+  | InactiveUnitFormProps;

공식 문서 참고: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/model/types.ts` around lines 10 - 13, UnitFormProps
currently allows any combination of mode, unitIndex and handlers which weakens
type safety; change it to a discriminated union keyed on mode (the Mode type) by
creating separate interfaces (e.g., UnitFormCreateProps with mode: 'create' and
required onCreateUnit only; UnitFormEditProps with mode: 'edit', unitIndex:
number and required onUpdateUnit; UnitFormDeleteProps with mode: 'delete',
unitIndex: number and required onDeleteUnit) and export type UnitFormProps =
UnitFormCreateProps | UnitFormEditProps | UnitFormDeleteProps; ensure you
reference the exact symbols TUnitFormSchema, Mode, unitIndex, onCreateUnit,
onUpdateUnit and onDeleteUnit when renaming/organizing so callers and components
are updated to match the new discriminated union shape.
src/entities/assignment/model/schemas.ts (2)

13-19: 중첩 스키마 추출 고려

assignments 배열 내부의 객체 스키마가 인라인으로 정의되어 있습니다. 다른 곳에서 재사용 가능성이 있다면 별도 스키마로 추출하는 것도 좋습니다.

♻️ 중첩 스키마 추출 예시
+export const assignmentItemSchema = z.object({
+  course: z.string(),
+  section: z.string(),
+  assignment: z.string(),
+});
+
 export const assignmentScheduleSchema = z.object({
   date: z.string(),
   remainingDays: z.number(),
-  assignments: z.array(
-    z.object({
-      course: z.string(),
-      section: z.string(),
-      assignment: z.string(),
-    })
-  ),
+  assignments: z.array(assignmentItemSchema),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/assignment/model/schemas.ts` around lines 13 - 19, The inline
object schema used inside the assignments array should be extracted into a
reusable named schema: create a const like AssignmentItemSchema (or
AssignmentSchema) defined as z.object({ course: z.string(), section: z.string(),
assignment: z.string() }) and then replace the inline z.object(...) in
assignments with z.array(AssignmentItemSchema); export the new schema if it will
be reused elsewhere and update any type aliases or imports that should reference
AssignmentItemSchema instead of the inline object.

2-2: Zod import 스타일 일관성

import z from 'zod' (default import)를 사용하고 있는데, src/shared/model/schemas.ts에서는 import {z} from 'zod' (named import)를 사용합니다. 프로젝트 전체에서 일관된 스타일을 유지하는 것이 좋습니다.

참고: Zod는 두 방식 모두 지원하지만, named import가 더 일반적입니다.

♻️ named import로 통일
-import z from 'zod';
+import {z} from 'zod';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/assignment/model/schemas.ts` at line 2, Replace the default Zod
import used in this module ("import z from 'zod'") with the named import style
("import { z } from 'zod'") to match the project's existing convention (as seen
in the other schemas module); update any usages that reference the default
identifier if necessary so they continue to use the same symbol name "z" after
switching to the named import.
src/entities/unit/model/useUnitStore.ts (2)

15-15: 중복 export 패턴 정리 제안

useUnitStore가 named export (Line 15)와 default export (Line 51) 모두로 내보내지고 있습니다. 일관성을 위해 하나로 통일하는 것이 좋습니다. 프로젝트에서 주로 사용하는 패턴에 맞춰주세요.

♻️ named export만 유지하는 경우
 export const useUnitStore = create<UnitState>()(
   // ...
 );
-
-export default useUnitStore;

Also applies to: 51-51

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/model/useUnitStore.ts` at line 15, The module currently
exports useUnitStore twice (both a named export at "export const useUnitStore"
and a default export later), causing duplicate export patterns; normalize to a
single export style by removing the default export and keeping the named export
useUnitStore (or vice versa if your project prefers default exports), update any
import sites to match the chosen style, and ensure the symbol useUnitStore is
the single exported identifier from this file.

24-29: 객체 프로퍼티 단축 구문 사용 제안

ES6 단축 구문을 사용하면 더 간결해집니다.

♻️ 단축 구문 적용
       storeFormData: (title, releaseDate, dueDate) =>
         set({
-          title: title,
-          releaseDate: releaseDate,
-          dueDate: dueDate,
+          title,
+          releaseDate,
+          dueDate,
         }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/model/useUnitStore.ts` around lines 24 - 29, storeFormData
currently sets properties by repeating parameter names; update the object passed
to set in the storeFormData function to use ES6 property shorthand so it becomes
set({ title, releaseDate, dueDate }) instead of repeating each assignment;
modify the implementation in useUnitStore where storeFormData calls set to use
the shorthand while keeping the same parameter names and behavior.
src/shared/model/schemas.ts (1)

6-17: semesterCodeSchemasubmissionStatusSchema의 재사용성 확인

두 enum 스키마가 정의되어 있지만, src/shared/model/index.ts에서는 apiResponseSchema만 re-export되고 있습니다. 다른 모듈에서 이 스키마들을 직접 import하고 있다면 문제없지만, 일관된 public API를 위해 index.ts를 통한 re-export를 고려해 보세요.

♻️ index.ts에서 추가 export 제안
 export type {ApiResponse, UserType} from './common';
-export {apiResponseSchema} from './schemas';
+export {apiResponseSchema, semesterCodeSchema, submissionStatusSchema} from './schemas';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/model/schemas.ts` around lines 6 - 17, Re-export the two enum
schemas from the package entry so they’re available via the module index: add
semesterCodeSchema and submissionStatusSchema to the exports in the shared model
index (the module that currently re-exports apiResponseSchema) so other modules
can import them from the public API rather than hitting the schemas file
directly; update the index to export the symbols semesterCodeSchema and
submissionStatusSchema (from the schemas module) and run a quick compile/CI to
ensure no broken imports remain.
src/entities/unit/api/unitQueries.ts (1)

23-25: 간소화 가능한 arrow function

단일 표현식 반환이므로 중괄호와 return을 제거할 수 있습니다.

♻️ 간소화 제안
-      select: (data) => {
-        return data.response;
-      },
+      select: (data) => data.response,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/api/unitQueries.ts` around lines 23 - 25, Simplify the
arrow function used as the select handler by converting the block body to a
concise body: remove the curly braces and the explicit return in the select:
(data) => { return data.response; } function so it becomes a single-expression
arrow function that directly returns data.response (refer to the select handler
in src/entities/unit/api/unitQueries.ts).
src/pages/unit-editor/ui/UnitList.tsx (1)

14-23: handleSelectUnit 함수 간소화 가능

단순히 onUnitClick을 호출하는 wrapper 함수입니다. inline으로 직접 사용하거나, 함수를 제거하고 onClick에서 직접 호출할 수 있습니다.

♻️ 간소화 제안
 export const UnitList = ({
   unitList,
   onUnitClick,
   selectedUnitId,
   onAddNewUnit,
 }: UnitListProps) => {
-  const handleSelectUnit = (id: number) => {
-    onUnitClick(id);
-  };
-
   return (
     <div className='flex flex-col h-full'>
       ...
         {unitList?.map(({id, title, assignmentCount}) => (
           <li
-            onClick={() => handleSelectUnit(id)}
+            onClick={() => onUnitClick(id)}
             key={id}
             ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/ui/UnitList.tsx` around lines 14 - 23, The
handleSelectUnit wrapper in UnitList simply forwards its id to onUnitClick;
remove handleSelectUnit and call onUnitClick directly from the JSX onClick
handlers (or inline arrow functions) to simplify code and reduce indirection,
updating any references to handleSelectUnit in UnitListProps usage to use
onUnitClick instead.
src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx (1)

28-33: 버튼 접근성 및 type 속성 개선 필요

  1. 아이콘만 있는 버튼에는 스크린 리더를 위한 aria-label 추가가 권장됩니다.
  2. type="button" 명시를 통해 폼 내부에서 의도치 않은 submit 방지가 가능합니다.
♻️ 접근성 개선 제안
-      <button onClick={handleOnEdit}>
+      <button type="button" onClick={handleOnEdit} aria-label="수정">
         <EditIcon className='w-3.5 h-4' />
       </button>
-      <button onClick={handleOnDelete}>
+      <button type="button" onClick={handleOnDelete} aria-label="삭제">
         <DeleteIcon className='w-3.5 h-4' />
       </button>

참고: MDN - Button type attribute

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx` around lines
28 - 33, AssignmentManageActionsBar currently renders icon-only buttons for
edit/delete without type or accessible labels; update the two buttons (the ones
invoking handleOnEdit and handleOnDelete and rendering EditIcon/DeleteIcon) to
include type="button" to prevent accidental form submits and add descriptive
aria-label attributes (e.g., aria-label="Edit assignment" and aria-label="Delete
assignment") so screen readers can announce their purpose.
src/entities/unit/api/unitMutations.ts (1)

8-14: 파라미터 네이밍 불일치

createUnitunitForm을, updateUnitunit을 사용하고 있습니다. 동일한 TUnitFormSchema 타입을 받으므로 일관된 네이밍을 권장합니다.

♻️ 일관성 개선 제안
   // 단원 수정 뮤테이션 옵션
   updateUnit: {
     mutationKey: ['updateUnit'],
-    mutationFn: ({unitId, unit}: {unitId: number; unit: TUnitFormSchema}) =>
-      updateUnit(unitId, unit),
+    mutationFn: ({unitId, unitForm}: {unitId: number; unitForm: TUnitFormSchema}) =>
+      updateUnit(unitId, unitForm),
   },

Also applies to: 20-21

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/api/unitMutations.ts` around lines 8 - 14, The PR has
inconsistent parameter naming: createUnit expects unitForm while updateUnit uses
unit though both accept TUnitFormSchema; make names consistent across the
mutations by renaming the parameter in updateUnit (or in createUnit) to the same
identifier (e.g., unitForm) and adjust the mutationFn signature and any usages
accordingly so both createUnit and updateUnit accept a parameter named unitForm
of type TUnitFormSchema; update any related references in this file (including
the other occurrence around lines 20-21) to match the chosen name.
src/pages/select-assignment/AssignmentSelectPage.tsx (1)

38-41: 타입 안전성 개선 제안: backPath 타입 정의

location.state?.backPathany 타입으로 추론됩니다. React Router의 useLocation 제네릭을 활용하여 타입 안전성을 확보할 수 있습니다.

♻️ 타입 정의 예시
interface LocationState {
  backPath?: string;
}

const location = useLocation() as ReturnType<typeof useLocation> & {
  state: LocationState | null;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx` around lines 38 - 41,
location.state?.backPath is inferred as any; make useLocation generic to type
the location.state so backPath is typed (e.g., define an interface LocationState
with backPath?: string and cast/use useLocation with that type). Update the
location usage where returnToPreviousPage reads location.state?.backPath so it
uses the typed location (refer to useLocation and returnToPreviousPage) and keep
the navigate fallback (?? -1) unchanged.
src/entities/course/api/courseApi.ts (1)

6-12: 인라인 스키마 정의 대신 별도 스키마 추출 고려

getAllCourses 응답 스키마가 함수 내부에 인라인으로 정의되어 있습니다. 이 스키마를 schemas.ts에 별도로 정의하면 재사용성과 테스트 용이성이 향상됩니다.

♻️ 스키마 분리 제안

src/entities/course/model/schemas.ts에 추가:

export const courseListResponseSchema = z.object({
  count: z.number(),
  courses: z.array(dashboardCourseSchema),
});

그리고 courseApi.ts에서 사용:

-  return apiResponseSchema(
-    z.object({count: z.number(), courses: z.array(dashboardCourseSchema)})
-  ).parse(response.data);
+  return apiResponseSchema(courseListResponseSchema).parse(response.data);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/course/api/courseApi.ts` around lines 6 - 12, Extract the inline
response schema inside getAllCourses into a shared schema exported from
src/entities/course/model/schemas.ts (e.g. export const courseListResponseSchema
= z.object({count: z.number(), courses: z.array(dashboardCourseSchema)})), then
import and use that schema in courseApi.ts by replacing the inline z.object with
courseListResponseSchema when calling apiResponseSchema(...). Keep references to
dashboardCourseSchema and apiResponseSchema unchanged.
src/pages/unit-editor/UnitEditorPage.tsx (1)

39-45: 인덱스 계산 로직 개선 가능

IIFE(즉시 실행 함수)를 사용한 currentIndex 계산이 약간 복잡합니다. useMemo를 사용하면 의존성을 명확히 하고 불필요한 재계산을 방지할 수 있습니다.

♻️ useMemo 활용 제안
const currentIndex = useMemo(() => {
  if (currentMode === 'creating') {
    return (data?.unitCount ?? 0) + 1;
  }
  const index = data?.unitList?.findIndex((u) => u.id === currentUnitId);
  return index !== undefined && index >= 0 ? index + 1 : 1;
}, [currentMode, data?.unitCount, data?.unitList, currentUnitId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/UnitEditorPage.tsx` around lines 39 - 45, Replace the
IIFE that computes currentIndex with a useMemo so the value is recomputed only
when relevant dependencies change: move the logic inside a useMemo callback
referencing currentMode, data?.unitCount, data?.unitList, and currentUnitId;
keep the same branching (return (data?.unitCount ?? 0) + 1 when currentMode ===
'creating', otherwise find index via data?.unitList.findIndex(u => u.id ===
currentUnitId) and return index >= 0 ? index + 1 : 1) and export the memoized
result as currentIndex.
src/entities/student/model/schemas.ts (1)

22-23: 날짜 필드 검증 강화 고려

startDateendDatez.string()으로만 검증됩니다. API 응답이 항상 유효한 날짜 형식을 보장하지 않는다면, 추가 검증을 고려해보세요.

♻️ 날짜 검증 강화 예시
// ISO 날짜 형식 검증
startDate: z.string().datetime(),
// 또는 커스텀 정규식
startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),

Zod 날짜 검증 참고: Zod Strings - datetime

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/student/model/schemas.ts` around lines 22 - 23, Replace the weak
string-only validators for startDate and endDate in the student schema with a
stronger date validation: update the startDate and endDate validators in the
schema (the fields named startDate and endDate) to use z.string().datetime() for
ISO datetimes or z.string().regex(/^\d{4}-\d{2}-\d{2}$/) for YYYY-MM-DD, or
alternatively coerce to a Date with z.coerce.date() if you want Date objects;
ensure both fields are changed consistently and add tests to cover invalid
formats.
src/entities/student/model/types.ts (1)

12-12: 명확한 타입명 규칙 일관성을 위해 UnitStudentUnit으로 변경 고려

현재 src/entities/student/model/types.tsUnit 타입이 사용되지 않고 있어 실제 충돌은 없으나, 코드의 명확성과 일관성 측면에서 개선할 수 있습니다.

다른 student 타입들(StudentProgress, Student)이 명시적으로 접두사를 가지고 있는데 반해, Unitsrc/entities/unit/model/types.tsUnit과 동일한 이름을 사용하고 있습니다. 미래의 혼동을 방지하고 명확성을 높이기 위해 StudentUnit으로 변경하는 것을 권장합니다.

♻️ 타입명 변경 제안
-export type Unit = z.infer<typeof studentUnitSchema>;
+export type StudentUnit = z.infer<typeof studentUnitSchema>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/student/model/types.ts` at line 12, Rename the exported type
Unit to StudentUnit to avoid collision with the unit model and to match other
student-prefixed types: update the export in src/entities/student/model/types.ts
from "export type Unit = z.infer<typeof studentUnitSchema>;" to "export type
StudentUnit = z.infer<typeof studentUnitSchema>;", then find and update all
usages/imports of Unit that refer to the student schema (e.g., places importing
Unit from this module) to import and use StudentUnit instead, and adjust any
tests/types that reference studentUnitSchema-derived types accordingly so
symbols remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/App.tsx`:
- Line 16: Update the import for AssignmentManagePage to use the project's
absolute path convention instead of a relative path: replace the current import
of AssignmentManagePage (import ... from
'./pages/manage-assignment/AssignmentManagePage') with the absolute import using
the "@/pages/manage-assignment/AssignmentManagePage" module specifier so it
matches the other page imports and coding guidelines.

In `@src/entities/auth/model/schemas.ts`:
- Around line 3-10: kakaoLoginResponseSchema currently requires studentId for
all roles causing parse errors when ADMIN responses omit it; update the schema
so role discriminates responses: use z.discriminatedUnion('role', [...]) (or
equivalent) to define two branches where ROLE === 'USER' requires studentId:
z.string(), and ROLE === 'ADMIN' allows studentId to be
z.string().nullable().optional() (or omitted), then use this updated
kakaoLoginResponseSchema in the kakaoLogin parse call so ADMIN responses no
longer throw.

In `@src/entities/unit/api/unitApi.ts`:
- Around line 18-21: getUnitById currently accepts unitId: number | null but
calls privateAxios.get(`/units/${unitId}`) without guarding against null; add a
defensive check in getUnitById to early-return or throw when unitId is
null/undefined (e.g., throw new Error or return a rejected promise) so no
request to `/units/null` is made, or change the function signature to accept
only number and rely on callers to validate — update the getUnitById function to
perform the null check before calling privateAxios.get and document the
behavior.

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts`:
- Around line 12-27: The allAssignments query is always running even when a
course is selected and the truthy check treats 0 as falsy; update the useQuery
call for getAllAssignments() to run conditionally (add enabled: selectedCourseId
== null or enabled: selectedCourseId === undefined) so it only executes when no
course is selected, and change the selection check in the return logic to an
explicit null/undefined check (e.g., selectedCourseId != null) so courseId 0 is
treated as valid; keep getAssignmentsByCourse()'s enabled behavior but ensure it
uses the same explicit null/undefined condition to locate and return
course.assignments via unique().

In `@src/pages/manage-assignment/AssignmentManagePage.tsx`:
- Around line 88-89: AssignmentManagePage passes empty callbacks for the
required AssignmentPageLayout props onCancel and onConfirm; change the contract
so unused callbacks are optional by updating AssignmentPageLayout's prop types
(make onCancel?: () => void and onConfirm?: () => void) and update its render
logic to only show the confirm/cancel buttons when both callbacks are provided
(e.g., guard rendering with onCancel && onConfirm), or alternatively replace
AssignmentManagePage with a layout that doesn't expect those callbacks if you
prefer the separate-layout approach.

In `@src/shared/ui/list-row/list-row-styles.ts`:
- Line 4: The ListRow style currently lacks cursor feedback; update the
clickable state so rows show a pointer cursor: modify the ListRow rendering
(e.g., where the base style string in list-row-styles.ts and the ListRow
component consumes it) to append "cursor-pointer" when an onClick handler is
provided (or when the parent like AssignmentListContainer/AssignmentSelectPage
intends the row to be clickable), ensuring the className composition for base
(the 'base' string) or the element that renders the <li> includes
"cursor-pointer" only for interactive rows.

---

Outside diff comments:
In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Around line 66-73: The submit handler releases isSubmitting too early because
onCreateUnit/onUpdateUnit are currently synchronous (void); update
UnitEditorPage.tsx to call the TanStack Query mutateAsync (e.g., await
addUnit.mutateAsync({...}) / await updateUnit.mutateAsync(...)) and change the
exported UnitFormProps so onCreateUnit: (unit: TUnitFormSchema) => Promise<void>
and onUpdateUnit: (unitId: number, unit: TUnitFormSchema) => Promise<void>; then
make UnitForm.tsx's onSubmit async and await the calls to
onCreateUnit/onUpdateUnit (refer to onSubmit, UnitFormProps,
onCreateUnit/onUpdateUnit, and addUnit/updateUnit mutateAsync) so handleSubmit
sees the Promise and keeps isSubmitting until mutation completes.

---

Nitpick comments:
In `@src/entities/assignment/model/schemas.ts`:
- Around line 13-19: The inline object schema used inside the assignments array
should be extracted into a reusable named schema: create a const like
AssignmentItemSchema (or AssignmentSchema) defined as z.object({ course:
z.string(), section: z.string(), assignment: z.string() }) and then replace the
inline z.object(...) in assignments with z.array(AssignmentItemSchema); export
the new schema if it will be reused elsewhere and update any type aliases or
imports that should reference AssignmentItemSchema instead of the inline object.
- Line 2: Replace the default Zod import used in this module ("import z from
'zod'") with the named import style ("import { z } from 'zod'") to match the
project's existing convention (as seen in the other schemas module); update any
usages that reference the default identifier if necessary so they continue to
use the same symbol name "z" after switching to the named import.

In `@src/entities/auth/api/authApi.ts`:
- Line 3: The import of kakaoLoginResponseSchema in authApi.ts uses a relative
path; change the import to use the project's absolute alias (e.g. replace
"../model/schemas" with an absolute import like "@/entities/auth/model/schemas")
so the kakaoLoginResponseSchema import follows the `@/`... convention used across
the codebase and remains stable during refactors.

In `@src/entities/course/api/courseApi.ts`:
- Around line 6-12: Extract the inline response schema inside getAllCourses into
a shared schema exported from src/entities/course/model/schemas.ts (e.g. export
const courseListResponseSchema = z.object({count: z.number(), courses:
z.array(dashboardCourseSchema)})), then import and use that schema in
courseApi.ts by replacing the inline z.object with courseListResponseSchema when
calling apiResponseSchema(...). Keep references to dashboardCourseSchema and
apiResponseSchema unchanged.

In `@src/entities/student/model/schemas.ts`:
- Around line 22-23: Replace the weak string-only validators for startDate and
endDate in the student schema with a stronger date validation: update the
startDate and endDate validators in the schema (the fields named startDate and
endDate) to use z.string().datetime() for ISO datetimes or
z.string().regex(/^\d{4}-\d{2}-\d{2}$/) for YYYY-MM-DD, or alternatively coerce
to a Date with z.coerce.date() if you want Date objects; ensure both fields are
changed consistently and add tests to cover invalid formats.

In `@src/entities/student/model/types.ts`:
- Line 12: Rename the exported type Unit to StudentUnit to avoid collision with
the unit model and to match other student-prefixed types: update the export in
src/entities/student/model/types.ts from "export type Unit = z.infer<typeof
studentUnitSchema>;" to "export type StudentUnit = z.infer<typeof
studentUnitSchema>;", then find and update all usages/imports of Unit that refer
to the student schema (e.g., places importing Unit from this module) to import
and use StudentUnit instead, and adjust any tests/types that reference
studentUnitSchema-derived types accordingly so symbols remain consistent.

In `@src/entities/unit/api/unitMutations.ts`:
- Around line 8-14: The PR has inconsistent parameter naming: createUnit expects
unitForm while updateUnit uses unit though both accept TUnitFormSchema; make
names consistent across the mutations by renaming the parameter in updateUnit
(or in createUnit) to the same identifier (e.g., unitForm) and adjust the
mutationFn signature and any usages accordingly so both createUnit and
updateUnit accept a parameter named unitForm of type TUnitFormSchema; update any
related references in this file (including the other occurrence around lines
20-21) to match the chosen name.

In `@src/entities/unit/api/unitQueries.ts`:
- Around line 23-25: Simplify the arrow function used as the select handler by
converting the block body to a concise body: remove the curly braces and the
explicit return in the select: (data) => { return data.response; } function so
it becomes a single-expression arrow function that directly returns
data.response (refer to the select handler in
src/entities/unit/api/unitQueries.ts).

In `@src/entities/unit/model/useUnitStore.ts`:
- Line 15: The module currently exports useUnitStore twice (both a named export
at "export const useUnitStore" and a default export later), causing duplicate
export patterns; normalize to a single export style by removing the default
export and keeping the named export useUnitStore (or vice versa if your project
prefers default exports), update any import sites to match the chosen style, and
ensure the symbol useUnitStore is the single exported identifier from this file.
- Around line 24-29: storeFormData currently sets properties by repeating
parameter names; update the object passed to set in the storeFormData function
to use ES6 property shorthand so it becomes set({ title, releaseDate, dueDate })
instead of repeating each assignment; modify the implementation in useUnitStore
where storeFormData calls set to use the shorthand while keeping the same
parameter names and behavior.

In `@src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx`:
- Around line 28-33: AssignmentManageActionsBar currently renders icon-only
buttons for edit/delete without type or accessible labels; update the two
buttons (the ones invoking handleOnEdit and handleOnDelete and rendering
EditIcon/DeleteIcon) to include type="button" to prevent accidental form submits
and add descriptive aria-label attributes (e.g., aria-label="Edit assignment"
and aria-label="Delete assignment") so screen readers can announce their
purpose.

In `@src/pages/select-assignment/AssignmentSelectPage.tsx`:
- Around line 38-41: location.state?.backPath is inferred as any; make
useLocation generic to type the location.state so backPath is typed (e.g.,
define an interface LocationState with backPath?: string and cast/use
useLocation with that type). Update the location usage where
returnToPreviousPage reads location.state?.backPath so it uses the typed
location (refer to useLocation and returnToPreviousPage) and keep the navigate
fallback (?? -1) unchanged.

In `@src/pages/unit-editor/model/types.ts`:
- Around line 10-13: UnitFormProps currently allows any combination of mode,
unitIndex and handlers which weakens type safety; change it to a discriminated
union keyed on mode (the Mode type) by creating separate interfaces (e.g.,
UnitFormCreateProps with mode: 'create' and required onCreateUnit only;
UnitFormEditProps with mode: 'edit', unitIndex: number and required
onUpdateUnit; UnitFormDeleteProps with mode: 'delete', unitIndex: number and
required onDeleteUnit) and export type UnitFormProps = UnitFormCreateProps |
UnitFormEditProps | UnitFormDeleteProps; ensure you reference the exact symbols
TUnitFormSchema, Mode, unitIndex, onCreateUnit, onUpdateUnit and onDeleteUnit
when renaming/organizing so callers and components are updated to match the new
discriminated union shape.

In `@src/pages/unit-editor/ui/UnitList.tsx`:
- Around line 14-23: The handleSelectUnit wrapper in UnitList simply forwards
its id to onUnitClick; remove handleSelectUnit and call onUnitClick directly
from the JSX onClick handlers (or inline arrow functions) to simplify code and
reduce indirection, updating any references to handleSelectUnit in UnitListProps
usage to use onUnitClick instead.

In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 39-45: Replace the IIFE that computes currentIndex with a useMemo
so the value is recomputed only when relevant dependencies change: move the
logic inside a useMemo callback referencing currentMode, data?.unitCount,
data?.unitList, and currentUnitId; keep the same branching (return
(data?.unitCount ?? 0) + 1 when currentMode === 'creating', otherwise find index
via data?.unitList.findIndex(u => u.id === currentUnitId) and return index >= 0
? index + 1 : 1) and export the memoized result as currentIndex.

In `@src/shared/model/schemas.ts`:
- Around line 6-17: Re-export the two enum schemas from the package entry so
they’re available via the module index: add semesterCodeSchema and
submissionStatusSchema to the exports in the shared model index (the module that
currently re-exports apiResponseSchema) so other modules can import them from
the public API rather than hitting the schemas file directly; update the index
to export the symbols semesterCodeSchema and submissionStatusSchema (from the
schemas module) and run a quick compile/CI to ensure no broken imports remain.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c2b834 and 47eda8a.

📒 Files selected for processing (39)
  • src/App.tsx
  • src/entities/assignment/api/assignmentApi.ts
  • src/entities/assignment/api/assignmentMutations.ts
  • src/entities/assignment/api/assignmentQueries.ts
  • src/entities/assignment/api/assignmentQueryOptions.ts
  • src/entities/assignment/model/schemas.ts
  • src/entities/assignment/model/types.ts
  • src/entities/auth/api/authApi.ts
  • src/entities/auth/model/schemas.ts
  • src/entities/course/api/courseApi.ts
  • src/entities/course/api/courseMutations.ts
  • src/entities/course/api/courseQueries.ts
  • src/entities/course/api/courseQueryOptions.ts
  • src/entities/course/index.ts
  • src/entities/course/model/schemas.ts
  • src/entities/course/model/types.ts
  • src/entities/student/model/schemas.ts
  • src/entities/student/model/types.ts
  • src/entities/unit/api/unitApi.ts
  • src/entities/unit/api/unitMutations.ts
  • src/entities/unit/api/unitQueries.ts
  • src/entities/unit/model/types.ts
  • src/entities/unit/model/useUnitStore.ts
  • src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts
  • src/pages/dashboard/Dashboard.tsx
  • src/pages/manage-assignment/AssignmentManagePage.tsx
  • src/pages/manage-assignment/ui/AssignmentManageActionsBar.tsx
  • src/pages/select-assignment/AssignmentSelectPage.tsx
  • src/pages/select-assignment/ui/AssignmentListContainer.tsx
  • src/pages/unit-editor/UnitEditorPage.tsx
  • src/pages/unit-editor/model/types.ts
  • src/pages/unit-editor/ui/UnitForm.tsx
  • src/pages/unit-editor/ui/UnitList.tsx
  • src/shared/model/index.ts
  • src/shared/model/schemas.ts
  • src/shared/ui/Layout.tsx
  • src/shared/ui/button/Button.tsx
  • src/shared/ui/list-row/ListRow.tsx
  • src/shared/ui/list-row/list-row-styles.ts
💤 Files with no reviewable changes (3)
  • src/entities/course/api/courseQueryOptions.ts
  • src/entities/course/index.ts
  • src/entities/assignment/api/assignmentQueryOptions.ts

Comment on lines +3 to +10
export const kakaoLoginResponseSchema = z.object({
memberId: z.number(),
name: z.string(),
role: z.enum(['ADMIN', 'USER']),
studentId: z.string(),
email: z.string().nullable(),
accessToken: z.string(),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

studentId 스키마가 역할별 응답 차이를 막아 로그인 실패를 유발할 수 있어요

Line 7의 studentId: z.string()는 항상 필수 문자열을 강제합니다. 그런데 현재 호출부(kakaoLogin)는 roleADMIN | USER이고 studentId는 optional이라, ADMIN 응답에서 studentId가 없거나 null이면 parse가 즉시 throw 되어 로그인 플로우가 깨질 수 있습니다.

개선 제안 (역할별 응답을 명시적으로 검증)
 export const kakaoLoginResponseSchema = z.object({
   memberId: z.number(),
   name: z.string(),
-  role: z.enum(['ADMIN', 'USER']),
-  studentId: z.string(),
+  role: z.enum(['ADMIN', 'USER']),
+  studentId: z.string().nullable().optional(),
   email: z.string().nullable(),
   accessToken: z.string(),
 });

필요하면 더 엄격하게 z.discriminatedUnion('role', ...)으로 USER일 때만 studentId 필수로 강제하는 방식이 가장 안전합니다.

공식 문서: https://zod.dev/?id=optionals , https://zod.dev/?id=discriminated-unions

📝 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
export const kakaoLoginResponseSchema = z.object({
memberId: z.number(),
name: z.string(),
role: z.enum(['ADMIN', 'USER']),
studentId: z.string(),
email: z.string().nullable(),
accessToken: z.string(),
});
export const kakaoLoginResponseSchema = z.object({
memberId: z.number(),
name: z.string(),
role: z.enum(['ADMIN', 'USER']),
studentId: z.string().nullable().optional(),
email: z.string().nullable(),
accessToken: z.string(),
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/auth/model/schemas.ts` around lines 3 - 10,
kakaoLoginResponseSchema currently requires studentId for all roles causing
parse errors when ADMIN responses omit it; update the schema so role
discriminates responses: use z.discriminatedUnion('role', [...]) (or equivalent)
to define two branches where ROLE === 'USER' requires studentId: z.string(), and
ROLE === 'ADMIN' allows studentId to be z.string().nullable().optional() (or
omitted), then use this updated kakaoLoginResponseSchema in the kakaoLogin parse
call so ADMIN responses no longer throw.

Comment on lines +12 to +27
const {data: allAssignments} = useQuery(
assignmentQueries.getAllAssignments()
);
const {data: assignments} = useQuery(
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
);

if (selectedCourseId) {
return unique(
assignments?.response.courses.flatMap((course) =>
course.id === selectedCourseId ? course.assignments : []
) ?? []
);
}

return unique(allAssignments?.response.assignments ?? []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

sed -n '1,50p' src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 926


🏁 Script executed:

find . -name "assignmentQueries*" -type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 123


🏁 Script executed:

rg -t ts "assignmentQueries\.(getAllAssignments|getAssignmentsByCourse)" -A 3 -B 1

Repository: 2025-snowCode/snowCode-Client

Length of output: 1539


🏁 Script executed:

cat -n src/entities/assignment/api/assignmentQueries.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 1036


🏁 Script executed:

grep -r "mock\|Mock" src/entities/assignment/api/ -A 2 -B 2

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


선택 강의가 있을 때 전체 과제 쿼리가 불필요하게 실행됩니다.

getAssignmentsByCourse()는 이미 enabled: !!courseId 조건이 있지만, getAllAssignments()selectedCourseId 값과 관계없이 항상 실행됩니다. 또한 Line 19의 truthy 체크는 0도 유효한 강의 ID일 수 있으므로 명시적인 null 체크가 필요합니다.

🔧 제안 수정안
  const {data: allAssignments} = useQuery(
-   assignmentQueries.getAllAssignments()
+   {
+     ...assignmentQueries.getAllAssignments(),
+     enabled: selectedCourseId === null,
+   }
  );

- if (selectedCourseId) {
+ if (selectedCourseId !== null) {

관련 문서

📝 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 {data: allAssignments} = useQuery(
assignmentQueries.getAllAssignments()
);
const {data: assignments} = useQuery(
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
);
if (selectedCourseId) {
return unique(
assignments?.response.courses.flatMap((course) =>
course.id === selectedCourseId ? course.assignments : []
) ?? []
);
}
return unique(allAssignments?.response.assignments ?? []);
const {data: allAssignments} = useQuery(
{
...assignmentQueries.getAllAssignments(),
enabled: selectedCourseId === null,
}
);
const {data: assignments} = useQuery(
assignmentQueries.getAssignmentsByCourse(selectedCourseId ?? 0)
);
if (selectedCourseId !== null) {
return unique(
assignments?.response.courses.flatMap((course) =>
course.id === selectedCourseId ? course.assignments : []
) ?? []
);
}
return unique(allAssignments?.response.assignments ?? []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/features/assignment/filter-assignmnet/lib/useAssignmentList.ts` around
lines 12 - 27, The allAssignments query is always running even when a course is
selected and the truthy check treats 0 as falsy; update the useQuery call for
getAllAssignments() to run conditionally (add enabled: selectedCourseId == null
or enabled: selectedCourseId === undefined) so it only executes when no course
is selected, and change the selection check in the return logic to an explicit
null/undefined check (e.g., selectedCourseId != null) so courseId 0 is treated
as valid; keep getAssignmentsByCourse()'s enabled behavior but ensure it uses
the same explicit null/undefined condition to locate and return
course.assignments via unique().

Comment on lines +88 to +89
onCancel={() => {}}
onConfirm={() => {}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the AssignmentManagePage.tsx file and examine lines 88-89
if git ls-files src/pages/manage-assignment/AssignmentManagePage.tsx; then
  echo "=== File found, examining lines 80-95 ==="
  sed -n '80,95p' src/pages/manage-assignment/AssignmentManagePage.tsx
  echo ""
  echo "=== Total line count ==="
  wc -l src/pages/manage-assignment/AssignmentManagePage.tsx
fi

Repository: 2025-snowCode/snowCode-Client

Length of output: 547


🏁 Script executed:

#!/bin/bash
# Search for AssignmentPageLayout definition
echo "=== Searching for AssignmentPageLayout component ==="
fd -t f -e tsx -e ts | xargs rg -l "AssignmentPageLayout" | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 338


🏁 Script executed:

#!/bin/bash
# Look for AssignmentPageLayout in widgets directory
echo "=== AssignmentPageLayout files ==="
fd -t f "AssignmentPageLayout" src/

Repository: 2025-snowCode/snowCode-Client

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Examine AssignmentPageLayout component definition
echo "=== AssignmentPageLayout component ==="
cat src/widgets/assignment-page-layout/ui/AssignmentPageLayout.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 1381


🏁 Script executed:

#!/bin/bash
# Check how AssignmentPageLayout is used in AssignmentSelectPage
echo "=== AssignmentSelectPage usage ==="
sed -n '1,150p' src/pages/select-assignment/AssignmentSelectPage.tsx | tail -50

Repository: 2025-snowCode/snowCode-Client

Length of output: 1369


🏁 Script executed:

#!/bin/bash
# Check full context of AssignmentManagePage.tsx
echo "=== Full AssignmentManagePage.tsx ==="
cat -n src/pages/manage-assignment/AssignmentManagePage.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 3822


AssignmentPageLayout의 필수 props와 실제 사용 패턴의 불일치 해결 필요

AssignmentPageLayout 컴포넌트의 props 정의를 확인한 결과, onCancelonConfirm이 필수 props입니다. 그러나 현재 페이지에서는 두 콜백 모두 빈 함수로 전달되고 있습니다.

현황 분석:

  • AssignmentSelectPage는 사용자가 문제를 선택하고 확인/취소하는 명확한 워크플로우가 있어 이 props가 의미 있습니다.
  • AssignmentManagePage는 문제를 조회하고 삭제하는 페이지로, 확인/취소 버튼의 용도가 불명확합니다. 실제로 사용자의 의도를 반영하는 로직이 없습니다.

개선 방안:

  1. 권장: AssignmentPageLayout에서 onCancelonConfirm을 선택적 props으로 변경하고, 이 props가 없을 때는 버튼을 렌더링하지 않도록 수정하세요.

    interface AssignmentPageLayoutProps {
      // ...
      onCancel?: () => void;
      onConfirm?: () => void;
    }
    // 렌더링 시: {onCancel && onConfirm && <div className='...'>...</div>}
  2. 대체 방안: 페이지의 실제 필요에 맞는 별도의 레이아웃 컴포넌트 사용을 검토하세요.

현재 형태는 사용하지 않는 UI 요소가 사용자에게 노출될 수 있어 좋은 사용성 경험을 제공하지 못합니다. React Props 패턴 가이드를 참고하시면 도움이 될 것 같습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/manage-assignment/AssignmentManagePage.tsx` around lines 88 - 89,
AssignmentManagePage passes empty callbacks for the required
AssignmentPageLayout props onCancel and onConfirm; change the contract so unused
callbacks are optional by updating AssignmentPageLayout's prop types (make
onCancel?: () => void and onConfirm?: () => void) and update its render logic to
only show the confirm/cancel buttons when both callbacks are provided (e.g.,
guard rendering with onCancel && onConfirm), or alternatively replace
AssignmentManagePage with a layout that doesn't expect those callbacks if you
prefer the separate-layout approach.


export const ListRowStyles = tv({
base: 'cursor-pointer bg-background w-full flex items-center rounded-[9px] pl-4.5 pr-7.5 py-4 gap-4 border',
base: 'bg-background w-full flex items-center rounded-[9px] pl-4.5 pr-7.5 py-4 gap-4 border',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find ListRow usage patterns - searching for clickable instances
rg "ListRow" --type=tsx --type=ts -B 2 -A 3

Repository: 2025-snowCode/snowCode-Client

Length of output: 100


🏁 Script executed:

# Check the ListRow component definition
find . -name "*list-row*" -type f | head -20

Repository: 2025-snowCode/snowCode-Client

Length of output: 116


🏁 Script executed:

# Look for the ListRow component implementation
cat src/shared/ui/list-row/list-row.tsx 2>/dev/null || find . -path "*/list-row/*" -name "*.tsx" -o -name "*.ts"

Repository: 2025-snowCode/snowCode-Client

Length of output: 50386


🏁 Script executed:

cat src/shared/ui/list-row/ListRow.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 700


🏁 Script executed:

# Search for ListRow usage with correct syntax
rg "ListRow" --include="*.tsx" --include="*.ts" -B 2 -A 3 | head -100

Repository: 2025-snowCode/snowCode-Client

Length of output: 514


🏁 Script executed:

cat src/shared/ui/list-row/list-row-styles.ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 485


🏁 Script executed:

# Search for ListRow usage with correct syntax
rg "ListRow" -A 3 | head -150

Repository: 2025-snowCode/snowCode-Client

Length of output: 4072


🏁 Script executed:

rg "ListRow" -A 10 | grep -E "ListRow|onClick" | head -50

Repository: 2025-snowCode/snowCode-Client

Length of output: 2387


🏁 Script executed:

# Check the AssignmentSelectPage to see if ListRow is clickable there
cat src/pages/select-assignment/AssignmentSelectPage.tsx | head -100

Repository: 2025-snowCode/snowCode-Client

Length of output: 2555


🏁 Script executed:

# Check AssignmentListContainer to see how ListRow is wrapped
rg "AssignmentListContainer" -A 20 | head -80

Repository: 2025-snowCode/snowCode-Client

Length of output: 7506


🏁 Script executed:

# Look for the actual AssignmentListContainer file
find . -name "*AssignmentListContainer*" -type f

Repository: 2025-snowCode/snowCode-Client

Length of output: 133


🏁 Script executed:

cat src/pages/select-assignment/ui/AssignmentListContainer.tsx

Repository: 2025-snowCode/snowCode-Client

Length of output: 1151


🏁 Script executed:

# Verify if cursor-pointer is applied to the <li> element in AssignmentListContainer or elsewhere
rg "cursor-pointer" -n src/pages/select-assignment/

Repository: 2025-snowCode/snowCode-Client

Length of output: 55


ListRow 클릭 가능 시 커서 피드백 추가 필요

기본 스타일에서 cursor-pointer가 제거되었는데, AssignmentSelectPage 같은 페이지에서 ListRow가 클릭 가능한 요소로 사용될 때는 사용자가 상호작용 가능 여부를 인지하기 어렵습니다.

AssignmentListContainer<li> 요소에 cursor-pointer를 추가하거나, ListRow에 onClick 핸들러를 전달할 때 스타일을 함께 적용하는 방식으로 개선해주세요. 이렇게 하면 클릭 가능한 행과 그렇지 않은 행을 명확히 구분할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/ui/list-row/list-row-styles.ts` at line 4, The ListRow style
currently lacks cursor feedback; update the clickable state so rows show a
pointer cursor: modify the ListRow rendering (e.g., where the base style string
in list-row-styles.ts and the ListRow component consumes it) to append
"cursor-pointer" when an onClick handler is provided (or when the parent like
AssignmentListContainer/AssignmentSelectPage intends the row to be clickable),
ensuring the className composition for base (the 'base' string) or the element
that renders the <li> includes "cursor-pointer" only for interactive rows.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/entities/auth/api/authApi.ts (1)

16-16: Zod 파싱 실패 시 에러 처리가 필요합니다.

kakaoLoginResponseSchema.parse()는 유효성 검증 실패 시 ZodError를 throw합니다. 현재 코드에서는 이 예외를 처리하지 않아, API 응답이 예상과 다를 경우 호출부에서 예기치 않은 에러가 발생할 수 있습니다.

두 가지 접근 방법을 고려해 보세요:

  1. safeParse 사용: 에러를 throw하지 않고 결과 객체로 반환
  2. try-catch로 감싸기: ZodError를 의미 있는 에러로 변환
🛡️ safeParse를 활용한 개선 예시
- const data = kakaoLoginResponseSchema.parse(response.data.response);
+ const result = kakaoLoginResponseSchema.safeParse(response.data.response);
+ if (!result.success) {
+   console.error('API 응답 검증 실패:', result.error.flatten());
+   throw new Error('로그인 응답 형식이 올바르지 않습니다.');
+ }
+ const data = result.data;

📚 참고: Zod 공식 문서 - safeParse

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/auth/api/authApi.ts` at line 16,
kakaoLoginResponseSchema.parse(response.data.response)에서 ZodError가 throw될 수 있으니
예외 처리를 추가하세요: 대신 kakaoLoginResponseSchema.safeParse(response.data.response)를 사용해
결과.ok를 체크하고 실패 시 적절한 에러 객체(예: ApiError 또는 명확한 메시지와 함께 status code)를 반환하거나, 기존 호출
흐름에 맞게 실패를 throw 하도록 구현하거나, 대안으로 parse 호출을 try-catch로 감싸고 catch에서 ZodError를 잡아
의미 있는 오류로 변환해서 던지거나 로깅하도록 변경하세요; 참조 대상 심볼: kakaoLoginResponseSchema.parse,
response.data.response, kakaoLoginResponseSchema.safeParse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/entities/student/model/types.ts`:
- Around line 3-8: The import uses `import type` for runtime Zod schemas
(progressStatusSchema, studentProgressSchema, studentUnitSchema, studentSchema)
which causes "Cannot use as a value because it was imported using 'import
type'"; change the import to a regular runtime import (remove `type`) so the Zod
schema symbols are available at runtime for `z.infer<typeof ...>` usage in this
file (types.ts) and keep existing `z.infer` usages unchanged.

In `@src/pages/course-overview/ui/CourseHero.tsx`:
- Line 70: In the CourseHero component update the invalid Tailwind classes
`pb-2.75` and `pb-12.5` (these don't exist in the project's Tailwind v4 spacing
scale) by either: 1) switching to a valid scale like `pb-2.5` / `pb-3` and
`pb-12` respectively, 2) using Tailwind arbitrary values such as `pb-[11px]` or
`pb-[2.75rem]`, or 3) adding custom spacing tokens to your Tailwind theme (in
the `@theme` spacing block) and then using those new keys; locate the class
names in the CourseHero JSX and apply one of these fixes consistently for both
occurrences.

---

Nitpick comments:
In `@src/entities/auth/api/authApi.ts`:
- Line 16: kakaoLoginResponseSchema.parse(response.data.response)에서 ZodError가
throw될 수 있으니 예외 처리를 추가하세요: 대신
kakaoLoginResponseSchema.safeParse(response.data.response)를 사용해 결과.ok를 체크하고 실패 시
적절한 에러 객체(예: ApiError 또는 명확한 메시지와 함께 status code)를 반환하거나, 기존 호출 흐름에 맞게 실패를 throw
하도록 구현하거나, 대안으로 parse 호출을 try-catch로 감싸고 catch에서 ZodError를 잡아 의미 있는 오류로 변환해서
던지거나 로깅하도록 변경하세요; 참조 대상 심볼: kakaoLoginResponseSchema.parse,
response.data.response, kakaoLoginResponseSchema.safeParse.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47eda8a and 6959037.

📒 Files selected for processing (8)
  • src/entities/auth/api/authApi.ts
  • src/entities/auth/model/useUserStore.ts
  • src/entities/student/model/types.ts
  • src/pages/course-overview/ui/CourseHero.tsx
  • src/shared/lib/course.ts
  • src/shared/model/index.ts
  • src/shared/model/schemas.ts
  • src/shared/model/type.ts
💤 Files with no reviewable changes (1)
  • src/shared/model/type.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/model/schemas.ts

<h1 className='pb-[1px] text-2xl font-medium leading-9'>{title}</h1>
<p className='pb-[11px] text-base font-normal leading-6'>
<h1 className='pb-px text-2xl font-medium leading-9'>{title}</h1>
<p className='pb-2.75 text-base font-normal leading-6'>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find tailwind config files
echo "=== Searching for tailwind config files ==="
fd -e js -e ts -e mjs -e cjs 'tailwind\.config' --exec cat {} 2>/dev/null

# Also search for tailwind configuration in package.json or other config formats
echo ""
echo "=== Checking for tailwind config in other formats ==="
find . -maxdepth 2 -name "tailwind.config.*" -type f 2>/dev/null | head -5

Repository: 2025-snowCode/snowCode-Client

Length of output: 171


🏁 Script executed:

#!/bin/bash
# Let's also check the actual file mentioned in the review
echo "=== Checking CourseHero.tsx file around line 70 ==="
if [ -f "src/pages/course-overview/ui/CourseHero.tsx" ]; then
  sed -n '65,75p' src/pages/course-overview/ui/CourseHero.tsx
else
  echo "File not found at expected path, searching for CourseHero.tsx"
  fd "CourseHero.tsx" --exec cat {} \; 2>/dev/null | head -100
fi

Repository: 2025-snowCode/snowCode-Client

Length of output: 550


🏁 Script executed:

#!/bin/bash
# Search for pb-2.75 usage in the codebase
echo "=== Searching for pb-2.75 usage ==="
rg "pb-2\.75" --type tsx --type ts

Repository: 2025-snowCode/snowCode-Client

Length of output: 153


🏁 Script executed:

#!/bin/bash
# Search for all config files at root
echo "=== Files at root level ==="
ls -la *.config.* 2>/dev/null || echo "No config files at root"

# Search for tailwind config in all locations
echo ""
echo "=== Searching for tailwind config anywhere ==="
find . -name "*tailwind*" -type f 2>/dev/null | grep -v node_modules | head -20

# Check if there's a tsconfig or jsconfig with paths
echo ""
echo "=== Checking tsconfig.json for custom paths ==="
if [ -f "tsconfig.json" ]; then
  cat tsconfig.json | head -30
fi

Repository: 2025-snowCode/snowCode-Client

Length of output: 514


🏁 Script executed:

#!/bin/bash
# Search for pb-2.75 in all files
echo "=== Searching for pb-2.75 in codebase ==="
rg "pb-2\.75" --type-list | head -20
rg "pb-2\.75" -t js -t ts --no-heading 2>/dev/null || rg "pb-2\.75" --no-heading || grep -r "pb-2\.75" . --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" 2>/dev/null | head -10

# Also check for custom spacing configuration
echo ""
echo "=== Searching for custom spacing in config files ==="
grep -r "pb-2\.75\|2\.75" . --include="*.json" --include="*.js" --include="*.ts" 2>/dev/null | grep -v node_modules | head -10

Repository: 2025-snowCode/snowCode-Client

Length of output: 681


🏁 Script executed:

#!/bin/bash
# Check package.json for Tailwind version and config
echo "=== Checking package.json ==="
if [ -f "package.json" ]; then
  cat package.json | grep -A 5 -B 5 "tailwind" || cat package.json | head -50
fi

# Search for any CSS configuration files
echo ""
echo "=== Checking for CSS and style configuration ==="
find . -maxdepth 3 -name "*.css" -type f 2>/dev/null | grep -v node_modules | head -10

Repository: 2025-snowCode/snowCode-Client

Length of output: 1114


🏁 Script executed:

#!/bin/bash
# Check the Tailwind CSS setup file
echo "=== Checking src/index.css ==="
cat src/index.css

echo ""
echo "=== Checking if there are any global CSS files with custom spacing ==="
find src -name "*.css" -type f 2>/dev/null | xargs cat 2>/dev/null | head -50

Repository: 2025-snowCode/snowCode-Client

Length of output: 3804


🏁 Script executed:

#!/bin/bash
# Check vite.config.ts for tailwind setup
echo "=== Checking vite.config.ts ==="
cat vite.config.ts

echo ""
echo "=== Check if pb-2.75 could be an arbitrary value in Tailwind v4 ==="
# In Tailwind v4, arbitrary values use square bracket notation like pb-[2.75rem]
# Let's verify the exact class being used
echo "Exact class in CourseHero.tsx:"
grep -o "pb-[^ ]*" src/pages/course-overview/ui/CourseHero.tsx | sort | uniq

Repository: 2025-snowCode/snowCode-Client

Length of output: 1004


pb-2.75는 Tailwind 기본 스페이싱 스케일에 없는 값입니다. 설정 필요

현재 프로젝트의 Tailwind CSS v4 설정에서 pb-2.75는 정의되지 않았습니다. 기본 스페이싱 스케일(2, 2.5, 3, 3.5...)에 포함되지 않는 값이므로 스타일이 적용되지 않습니다.

아래 중 하나를 선택하세요:

  1. src/index.css@theme 블록에 커스텀 스페이싱 추가
  2. 임의 값 사용: pb-[2.75rem] (또는 픽셀 단위로 pb-[11px])
  3. 기본 스케일 값으로 변경: pb-2.5 또는 pb-3

같은 파일의 pb-12.5도 동일한 문제가 있으니 함께 수정하세요.

📚 참고: Tailwind CSS v4 Theme Configuration

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/course-overview/ui/CourseHero.tsx` at line 70, In the CourseHero
component update the invalid Tailwind classes `pb-2.75` and `pb-12.5` (these
don't exist in the project's Tailwind v4 spacing scale) by either: 1) switching
to a valid scale like `pb-2.5` / `pb-3` and `pb-12` respectively, 2) using
Tailwind arbitrary values such as `pb-[11px]` or `pb-[2.75rem]`, or 3) adding
custom spacing tokens to your Tailwind theme (in the `@theme` spacing block) and
then using those new keys; locate the class names in the CourseHero JSX and
apply one of these fixes consistently for both occurrences.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/entities/student/model/types.ts (1)

3-8: 절대 경로 import로 통일해 주세요

Line 3-8은 상대 경로(./schemas)라서 프로젝트 규칙과 어긋납니다. alias 경로(@/...)로 맞추면 모듈 이동/리팩터링 시 안정성이 좋아집니다.
참고: https://nextjs.org/docs/app/building-your-application/configuring/absolute-imports-and-module-aliases

변경 제안
 import {
   progressStatusSchema,
   studentProgressSchema,
   studentUnitSchema,
   studentSchema,
-} from './schemas';
+} from '@/entities/student/model/schemas';

As per coding guidelines src/**: "절대 경로 임포트(@/...) 사용".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/student/model/types.ts` around lines 3 - 8, The import in
types.ts currently pulls progressStatusSchema, studentProgressSchema,
studentUnitSchema, and studentSchema via a relative path ('./schemas'); update
that import to use the project alias/absolute path (e.g., '@/...') so it follows
the src/** coding guideline and remains stable during refactors—locate the
import that references './schemas' and replace it with the corresponding alias
import for the same exported symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/entities/student/model/types.ts`:
- Around line 3-8: The import in types.ts currently pulls progressStatusSchema,
studentProgressSchema, studentUnitSchema, and studentSchema via a relative path
('./schemas'); update that import to use the project alias/absolute path (e.g.,
'@/...') so it follows the src/** coding guideline and remains stable during
refactors—locate the import that references './schemas' and replace it with the
corresponding alias import for the same exported symbols.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6959037 and 2607964.

📒 Files selected for processing (4)
  • src/App.tsx
  • src/entities/student/model/types.ts
  • src/entities/unit/api/unitApi.ts
  • src/entities/unit/api/unitQueries.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/App.tsx
  • src/entities/unit/api/unitQueries.ts
  • src/entities/unit/api/unitApi.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/entities/assignment/api/assignmentApi.ts (1)

4-4: 절대 경로 임포트로 통일해 주세요.

Line 4는 상대 경로(../model/schemas)를 사용하고 있어 이 파일의 나머지 임포트 스타일과도 불일치합니다. alias 경로(@/...)로 맞추는 편이 리팩터링 내성이 좋습니다.

변경 제안
-import {assignmentScheduleSchema} from '../model/schemas';
+import {assignmentScheduleSchema} from '@/entities/assignment/model/schemas';

As per coding guidelines src/**: "절대 경로 임포트(@/...) 사용".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/assignment/api/assignmentApi.ts` at line 4, Replace the relative
import of assignmentScheduleSchema with the project's absolute alias import
form; locate the import statement that references assignmentScheduleSchema and
change it from a "../" relative path to the alias-based path used across the
codebase (use the "@/..." alias to point to the model/schemas module) so imports
are consistent and refactor-safe.
src/entities/unit/model/schemas.ts (1)

23-25: 날짜 문자열 비교 방식 확인 필요

releaseDate <= dueDate 비교는 ISO 8601 형식(YYYY-MM-DD)일 때만 올바르게 동작합니다. 입력 형식이 다르거나 시간이 포함된 경우 예상치 못한 결과가 발생할 수 있습니다.

현재 type='date' 입력을 사용하고 있어 ISO 형식이 보장되지만, 방어적 코딩을 위해 Date 객체로 변환 후 비교하는 것도 고려해볼 수 있습니다:

💡 Date 객체 비교 방식 제안
  .refine((data) => data.releaseDate <= data.dueDate, {
+  // 또는 더 안전한 Date 비교:
+  // .refine((data) => new Date(data.releaseDate) <= new Date(data.dueDate), {
    message: '날짜 범위가 올바르지 않습니다.',
    path: ['dueDate'],
  });

Zod 공식 문서 - Refine

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/entities/unit/model/schemas.ts` around lines 23 - 25, The refine check in
schemas.ts currently compares releaseDate <= dueDate as strings; change the
.refine on the schema that references releaseDate and dueDate to parse both into
Date objects (e.g., via new Date(...) or Date.parse) and compare their numeric
time values (dateA.getTime() <= dateB.getTime()), also ensure you guard against
invalid dates (isNaN) and return false (with the same message/path) when parsing
fails so invalid inputs are rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Around line 167-175: The Button inside the UnitForm is rendered within a form
and currently relies on default behavior, so add an explicit type="button" to
the <Button> that uses onClick={handleAssignmentSelect} (the button reading “문제
연결”) to prevent accidental form submission when mode !== 'editing'; update the
JSX for that Button component to include type="button" while keeping
color='tonal', size='compact', content='mixed' and the disabled prop logic
unchanged.

---

Nitpick comments:
In `@src/entities/assignment/api/assignmentApi.ts`:
- Line 4: Replace the relative import of assignmentScheduleSchema with the
project's absolute alias import form; locate the import statement that
references assignmentScheduleSchema and change it from a "../" relative path to
the alias-based path used across the codebase (use the "@/..." alias to point to
the model/schemas module) so imports are consistent and refactor-safe.

In `@src/entities/unit/model/schemas.ts`:
- Around line 23-25: The refine check in schemas.ts currently compares
releaseDate <= dueDate as strings; change the .refine on the schema that
references releaseDate and dueDate to parse both into Date objects (e.g., via
new Date(...) or Date.parse) and compare their numeric time values
(dateA.getTime() <= dateB.getTime()), also ensure you guard against invalid
dates (isNaN) and return false (with the same message/path) when parsing fails
so invalid inputs are rejected.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2607964 and 1dbdb31.

📒 Files selected for processing (8)
  • src/entities/assignment/api/assignmentApi.ts
  • src/entities/assignment/model/types.ts
  • src/entities/course/model/schemas.ts
  • src/entities/course/model/types.ts
  • src/entities/unit/api/unitApi.ts
  • src/entities/unit/model/schemas.ts
  • src/entities/unit/model/types.ts
  • src/pages/unit-editor/ui/UnitForm.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/entities/assignment/model/types.ts
  • src/entities/course/model/schemas.ts
  • src/entities/unit/api/unitApi.ts
  • src/entities/unit/model/types.ts

Comment on lines +167 to 175
<Button
onClick={handleAssignmentSelect}
color='tonal'
size='compact'
content='mixed'
disabled={mode === 'editing'}>
<AddIcon className='w-3 h-3' />
문제 연결
</Button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

type="button" 명시 필요

<Button> 컴포넌트가 <form> 내부에 있어서, 기본 타입이 submit일 경우 클릭 시 의도치 않은 폼 제출이 발생할 수 있습니다. onClick 핸들러만 실행되도록 type="button"을 명시하세요.

🐛 수정 제안
              <Button
+               type='button'
                onClick={handleAssignmentSelect}
                color='tonal'
                size='compact'
                content='mixed'
                disabled={mode === 'editing'}>

MDN - Button type attribute

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/unit-editor/ui/UnitForm.tsx` around lines 167 - 175, The Button
inside the UnitForm is rendered within a form and currently relies on default
behavior, so add an explicit type="button" to the <Button> that uses
onClick={handleAssignmentSelect} (the button reading “문제 연결”) to prevent
accidental form submission when mode !== 'editing'; update the JSX for that
Button component to include type="button" while keeping color='tonal',
size='compact', content='mixed' and the disabled prop logic unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/pages/select-assignment/ui/AssignmentListContainer.tsx (1)

5-12: 제네릭 계약이 흐려져 타입 결합도가 올라갔습니다

AssignmentListContainerProps<T>는 제네릭인데 onSelect/handleSelectAssignment로 고정되어 있습니다. 현재 동작은 가능해도, 타입 소스가 2갈래라 이후 변경 시 깨지기 쉽습니다. 단일 타입(AssignmentCourse['assignments'][number])으로 통일하는 편이 유지보수에 유리합니다.
참고: https://www.typescriptlang.org/docs/handbook/2/generics.html

타입 통일 예시 diff
-import type {Assignment} from '@/entities/assignment/model/types';
 import type {AssignmentCourse} from '@/entities/course/model/types';
 import type {MouseEvent, ReactNode} from 'react';

-type T = AssignmentCourse['assignments'][number];
+type AssignmentItem = AssignmentCourse['assignments'][number];

-interface AssignmentListContainerProps<T> {
-  items: T[];
-  renderItem: (item: T) => ReactNode;
+interface AssignmentListContainerProps {
+  items: AssignmentItem[];
+  renderItem: (item: AssignmentItem) => ReactNode;
   title: string;
-  onSelect?: (item: Assignment) => void;
+  onSelect?: (item: AssignmentItem) => void;
 }

 const AssignmentListContainer = ({
   onSelect,
   items,
   renderItem,
   title,
-}: AssignmentListContainerProps<T>) => {
-  const handleSelect = (item: Assignment, event: MouseEvent) => {
+}: AssignmentListContainerProps) => {
+  const handleSelect = (item: AssignmentItem, event: MouseEvent<HTMLButtonElement>) => {
     event.stopPropagation();
     onSelect?.(item);
   };

Also applies to: 19-23

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx` around lines 5 -
12, AssignmentListContainerProps<T> currently accepts a generic T but the
onSelect/handleSelect callbacks are fixed to Assignment, creating a mixed
contract; change the props and related handlers to use a single concrete type
AssignmentCourse['assignments'][number] (remove/replace the generic T) so items,
renderItem, onSelect, and any handleSelect function all share the same type
(update AssignmentListContainerProps, the items prop, renderItem signature, and
handleSelect/onSelect declarations to reference
AssignmentCourse['assignments'][number] consistently).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx`:
- Around line 20-23: The list item click handler currently uses onClick directly
on the li (with handleSelect defined as (item,event: MouseEvent) => {
event.stopPropagation(); onSelect?.(item); }), which breaks keyboard
accessibility; move the interactive handler from the li to a focusable element
such as a button wrapped inside the li (or replace the li content with a
button), give the button type="button", attach the click handler (call
handleSelect or a small wrapper that calls onSelect) to that button, ensure
event.stopPropagation() is applied on the button's event, and add appropriate
keyboard/ARIA attributes (e.g., aria-selected or role if needed) so Enter/Space
activate the same behavior; update any tests/styles that assume li onClick
accordingly.

---

Nitpick comments:
In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx`:
- Around line 5-12: AssignmentListContainerProps<T> currently accepts a generic
T but the onSelect/handleSelect callbacks are fixed to Assignment, creating a
mixed contract; change the props and related handlers to use a single concrete
type AssignmentCourse['assignments'][number] (remove/replace the generic T) so
items, renderItem, onSelect, and any handleSelect function all share the same
type (update AssignmentListContainerProps, the items prop, renderItem signature,
and handleSelect/onSelect declarations to reference
AssignmentCourse['assignments'][number] consistently).

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dbdb31 and 292ca07.

📒 Files selected for processing (3)
  • src/pages/course-overview/ui/UnitItem.tsx
  • src/pages/select-assignment/ui/AssignmentListContainer.tsx
  • src/pages/unit-editor/ui/UnitList.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/unit-editor/ui/UnitList.tsx

Comment on lines +20 to 23
const handleSelect = (item: Assignment, event: MouseEvent) => {
event.stopPropagation();
onSelect(id);
onSelect?.(item);
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

li 직접 클릭 처리로 키보드 접근성이 깨집니다

Line 29처럼 lionClick을 직접 주면 키보드(Enter/Space)로 동일 동작을 보장하기 어렵습니다. 선택 가능한 항목은 button 같은 포커스 가능한 요소로 감싸는 쪽이 안전합니다.
참고: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button , https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/

접근성 개선 예시 diff
-  const handleSelect = (item: Assignment, event: MouseEvent) => {
+  const handleSelect = (item: Assignment, event: MouseEvent<HTMLButtonElement>) => {
     event.stopPropagation();
     onSelect?.(item);
   };
@@
-          <li onClick={(e) => handleSelect(item, e)} key={item.id}>
-            {renderItem(item)}
-          </li>
+          <li key={item.id}>
+            <button
+              type='button'
+              onClick={(e) => handleSelect(item, e)}
+              className='w-full text-left'
+            >
+              {renderItem(item)}
+            </button>
+          </li>

Also applies to: 29-30

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/select-assignment/ui/AssignmentListContainer.tsx` around lines 20 -
23, The list item click handler currently uses onClick directly on the li (with
handleSelect defined as (item,event: MouseEvent) => { event.stopPropagation();
onSelect?.(item); }), which breaks keyboard accessibility; move the interactive
handler from the li to a focusable element such as a button wrapped inside the
li (or replace the li content with a button), give the button type="button",
attach the click handler (call handleSelect or a small wrapper that calls
onSelect) to that button, ensure event.stopPropagation() is applied on the
button's event, and add appropriate keyboard/ARIA attributes (e.g.,
aria-selected or role if needed) so Enter/Space activate the same behavior;
update any tests/styles that assume li onClick accordingly.

Copy link
Contributor

@JiiminHa JiiminHa left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

스키마로 옮겼으면 type.ts의 수동 타입 정의는 삭제하고 z.infer<>로 통일하면 될 것 같아요!

Copy link
Contributor

Choose a reason for hiding this comment

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

얘도 마찬가지입니다! 걍 스키마 밑에 z.infer로 타입을 스키마에서 파생시키는 걸로 충분해요!

Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 이 파일은 어떤 상황에서 사용할 계획이신지 궁금합니다!
errors 필드가 swagger 예시 placeholder 그대로 들어가 있어서 여쭤봐요!

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

Labels

🧹 chore 프로젝트 설정 / 빌드 / 패키지 관리

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: Zod 스키마 기반 API 응답 타입 시스템 도입

2 participants