Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbit릴리스 노트
📋 WalkthroughFSD 아키텍처 기반 대규모 리팩토링으로, 컴포넌트를 공유/위젯 계층으로 재구성하고, Unit 관리 기능을 신규 추가하며, 타입 시스템을 정리하고 CI/CD 파이프라인을 구축했습니다. 📊 Changes
🔀 Sequence DiagramsequenceDiagram
participant User as 사용자
participant Editor as UnitEditorPage
participant API as unitApi
participant RQ as React Query
participant UI as UnitForm<br/>UnitList
participant DB as 서버
User->>Editor: 코스의 Unit 에디터 접근
Editor->>RQ: getAllUnitsByCourseId(courseId) 요청
RQ->>API: 데이터 조회
API->>DB: GET /units?courseId=...
DB-->>API: 유닛 목록 응답
API-->>RQ: 응답 반환
RQ-->>Editor: 데이터 업데이트
Editor->>UI: unitList, selectedUnitId 전달
UI->>UI: UnitList 렌더링 (선택 기능)
User->>Editor: "유닛 추가" 버튼 클릭
Editor->>Editor: mode = 'creating'
Editor->>UI: UnitForm에 빈 폼 제공
User->>UI: 폼 작성 (title, releaseDate, dueDate)
User->>UI: "저장" 버튼 클릭
UI->>RQ: createUnit({courseId, unit}) 뮤테이션 실행
RQ->>API: 새 유닛 생성
API->>DB: POST /units
DB-->>API: 생성된 유닛 응답
API-->>RQ: 응답 반환
RQ->>RQ: unitList 쿼리 무효화
RQ->>Editor: 뮤테이션 성공 콜백 (mode='idle', 알림)
Editor->>RQ: getAllUnitsByCourseId 재요청
RQ-->>Editor: 업데이트된 유닛 목록
Editor->>UI: 새 유닛 포함 목록 렌더링
🎯 Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75분 분석 근거:
🔗 Possibly Related PRs
📌 Suggested Labels
👥 Suggested Reviewers
💡 리뷰 포인트✨ 잘된 점
🔍 확인 필요 사항
🎓 개선 제안
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 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: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/course-overview/ui/AssignmentList.tsx (1)
49-54:⚠️ Potential issue | 🟠 Major과제 라우팅을
index대신id를 사용하도록 수정하세요.현재
/assignments/${index}로 네비게이션하는데,index는 목록 내 순서(1부터 시작)이므로 불안정합니다. 과제가 재정렬되거나 필터링되면 잘못된 과제로 이동하거나 존재하지 않는 경로로 이동할 수 있습니다.
Assignment타입에id: number가 정의되어 있고, 스프레드 연산자를 통해 전달되므로id를 사용할 수 있습니다.AssignmentItemProps에서id를 구조분해할당에 추가하고 라우팅 경로를 수정하세요:수정 방법
const AssignmentItem = ({ + id, title, index, submittedStatus, isOpen, }: AssignmentItemProps) => { // ...- <Link to={`/assignments/${index}`} className='min-w-0'> + <Link to={`/assignments/${id}`} className='min-w-0'>또한
/assignments/:id라우트가App.tsx에 정의되어 있는지 확인하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/course-overview/ui/AssignmentList.tsx` around lines 49 - 54, The Link in AssignmentList.tsx currently navigates to `/assignments/${index}` which is unstable; update the component to use the assignment's id instead by adding id to the props destructuring (in AssignmentItemProps / the component that receives {...}) and replace `/assignments/${index}` with `/assignments/${id}`; also confirm the route `/assignments/:id` is defined in App.tsx so the new URL resolves correctly.
🧹 Nitpick comments (18)
.github/workflows/release-drafter.yml (1)
26-33: 조건문에 중복이 있습니다.Line 27의
github.event_name == 'push'조건은 워크플로우가 이미push이벤트에서만 트리거되므로 항상true입니다. 간결함을 위해 제거할 수 있습니다.🧹 간결한 조건문으로 수정
- name: Publish Release - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + if: github.ref == 'refs/heads/main' uses: release-drafter/release-drafter@v6🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release-drafter.yml around lines 26 - 33, The if condition on the "Publish Release" step currently includes a redundant check of github.event_name == 'push'; remove that redundant part so the step uses only the branch check (i.e., keep if: github.ref == 'refs/heads/main') in the Publish Release step to simplify the condition; locate the step by its name "Publish Release" and the existing if: line and update it accordingly..github/workflows/ci-cd.yml (2)
53-68: 빌드가 두 번 수행됩니다 - 의도된 것인지 확인해 주세요.현재 워크플로우에서
pnpm build와vercel build가 모두 실행되어 빌드가 두 번 수행됩니다. CI 시간을 절약하고 싶다면, 둘 중 하나만 유지하는 것을 고려해 보세요:
- 옵션 1:
pnpm build만 유지 (빠른 검증용)- 옵션 2:
vercel build만 유지 (Vercel 환경과 동일한 검증)Vercel은 자체적으로 배포 시 빌드를 수행하므로, CI에서의 사전 검증 목적이 아니라면 중복일 수 있습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci-cd.yml around lines 53 - 68, The workflow currently runs two build steps ("Build" which runs `pnpm build` and "Simulate Vercel Build" which runs `npx vercel build`), causing duplicate builds; decide which you want and remove the other step to avoid redundant CI time. If you only need quick verification keep the `pnpm build` step and delete the `npx vercel build` step (and its env block), or if you need parity with Vercel keep `npx vercel build` and remove the `pnpm build` step (and its env block); ensure remaining step preserves any required environment variables (`VITE_KAKAO_REST_API_KEY`, `VITE_KAKAO_REDIRECT_URI`, `VITE_API_BASE_URL`) and that the "Pull Vercel Environment" step remains if you rely on those pulled envs.
1-12: 깔끔한 워크플로우 설정입니다! 👍동시성(concurrency) 설정으로 리소스를 효율적으로 관리하고 있네요. 다만, 보안 강화를 위해 명시적인
permissions선언을 추가하는 것을 권장드립니다.🛡️ 보안 강화를 위한 권한 명시 제안
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: build-and-deploy:GitHub Actions 보안 모범 사례에 따르면 최소 권한 원칙(principle of least privilege)을 적용하여 필요한 권한만 명시적으로 선언하는 것이 좋습니다. 참고: GitHub Actions Security Hardening
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/ci-cd.yml around lines 1 - 12, Add an explicit top-level permissions declaration in the workflow (introduce the permissions key near the top alongside on/concurrency) and set only the minimum GitHub Actions permissions required for this pipeline (e.g., contents: read, actions: read, pull-requests: write, id-token: write only if OIDC is used) instead of relying on defaults; update any steps that currently assume broader access to use the narrowed permissions and remove any unnecessary write rights. Ensure the new permissions block is placed above or next to the existing concurrency/on sections so reviewers can find it easily..github/template/release-drafter.yml (1)
5-23: 일부 커밋 태그가 카테고리에 매핑되지 않았습니다.코딩 가이드라인에 정의된 커밋 태그 중 일부가 Release Drafter 카테고리에 포함되지 않았습니다:
modify,add,remove,rename,perf,correct이 태그를 사용한 PR은 릴리즈 노트에서 분류되지 않을 수 있습니다. 해당 태그들을 적절한 카테고리에 추가하는 것을 권장합니다.
♻️ 누락된 태그 추가 제안
- title: '🚀 기능 추가' labels: - 'feat' + - 'add' - title: '♻️ 리팩토링' labels: - 'refactor' + - 'rename' + - 'modify' - title: '🐛 버그 수정' labels: - 'fix' + - 'correct' + - title: '⚡ 성능 개선' + labels: + - 'perf' - title: '🧰 그 외' labels: - 'chore' - 'ci/cd' - 'docs' - 'style' - 'build' - 'common' - 'test' + - 'remove'Based on learnings: 프로젝트에서 사용하는 표준 커밋 태그(modify, add, remove, rename, perf, correct 등)를 모두 매핑해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/template/release-drafter.yml around lines 5 - 23, The Release Drafter categories block is missing mappings for standard commit tags (modify, add, remove, rename, perf, correct); update the categories list so each missing tag is included under an appropriate category—e.g., add 'add' and 'perf' to the '🚀 기능 추가' (or create a new '성능' category for 'perf'), include 'modify', 'rename' and 'correct' under '♻️ 리팩토링' or '🧰 그 외' as appropriate, and map 'remove' to '🐛 버그 수정' or '🧰 그 외' depending on semantics—ensure the labels arrays in the categories block include these tag strings so PRs using those commit tags are classified by Release Drafter (locate and edit the categories section and the category titles '🚀 기능 추가', '♻️ 리팩토링', '🐛 버그 수정', '🧰 그 외' to add the missing labels).src/index.css (2)
96-98: 공용.input-field에 고정 폭(w-[295px])은 재사용 시 반응형 제약이 큽니다.공용 유틸이라면
w-full + max-w조합이 폼 재사용성과 화면 대응에 더 유리합니다.수정 제안
.input-field { - `@apply` bg-white w-[295px] px-[14.6px] py-[10.5px] rounded-[9px] border-[0.9px] border-purple-stroke focus:outline-1 focus:outline-primary; + `@apply` bg-white w-full max-w-[295px] px-[14.6px] py-[10.5px] rounded-[9px] border-[0.9px] border-purple-stroke focus:outline-1 focus:outline-primary; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.css` around lines 96 - 98, The .input-field utility currently uses a fixed width (w-[295px]) which limits responsiveness; change it to use w-full combined with a sensible max width (e.g., max-w-[295px] or an existing max-w token) so the rule reads: keep bg-white, px-[14.6px], py-[10.5px], rounded-[9px], border-[0.9px] border-purple-stroke and focus styles but replace w-[295px] with w-full and add max-w-[295px] to allow full-width behavior on small screens while capping width on larger ones.
31-31:box-sizing은:root만으로는 전체 요소에 전파되지 않습니다.현재 설정만으로는 하위 요소/의사요소가
content-box로 남을 수 있습니다. 전역 일관성을 위해 universal selector를 같이 두는 편이 안전합니다.수정 제안
:root { font-family: Pretendard; background-color: `#f6f5f8`; box-sizing: border-box; @@ } + +@layer base { + *, + *::before, + *::after { + box-sizing: border-box; + } +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.css` at line 31, The :root rule setting box-sizing: border-box doesn't propagate to all elements/pseudo-elements; add a rule using the universal selectors to ensure consistency by adding a selector block for *, *::before, *::after that sets box-sizing: inherit (so all elements inherit the :root value). Update the CSS near the existing :root/box-sizing declaration and target the universal selectors to enforce border-box across elements and pseudo-elements.src/pages/admin/assignments/AssignmentCreatePage.tsx (1)
42-44: 리스트 key에 대한 참고 사항입니다.현재
idx(인덱스)를 key로 사용하고 있는데, 추가만 가능한 현재 구현에서는 문제없습니다. 다만 추후 예제 삭제/재정렬 기능이 추가된다면 고유 ID 기반 key로 변경이 필요합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/admin/assignments/AssignmentCreatePage.tsx` around lines 42 - 44, The map over examples in AssignmentCreatePage uses the loop index (key={idx}) which will break identity if items are removed or reordered; change the key to a stable unique identifier from the example object (e.g., use example.id in the examples.map callback) or ensure a unique id is assigned when examples are created (generate and persist a UUID) so replace key={idx} with key={example.id} and update creation logic that produces examples to include that id.src/pages/admin/courses/CourseCreatePage.tsx (1)
53-54: 빈 핸들러 함수에 TODO 주석을 추가하면 좋겠습니다.
onCancel과onConfirm이 빈 함수로 되어 있는데, API 연동 단계에서 구현 예정이라면 주석으로 명시해두면 추후 작업 시 도움이 됩니다.💡 제안
- onCancel={() => {}} - onConfirm={() => {}} + onCancel={() => {}} // TODO: 취소 로직 구현 + onConfirm={() => {}} // TODO: 강의 생성 API 연동🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/admin/courses/CourseCreatePage.tsx` around lines 53 - 54, The empty inline handlers onCancel and onConfirm in the CourseCreatePage component should be annotated with a clear TODO comment (or replaced with small named placeholder handlers like handleCancel and handleConfirm) indicating they are intentionally unimplemented until API integration; update the JSX where onCancel={() => {}} and onConfirm={() => {}} are declared to include a TODO comment referencing the expected future behavior (e.g., "TODO: implement cancel behavior / call createCourse API") or call a placeholder function that logs a warning so future work and reviewers can see these are deliberate stubs.src/pages/unit-editor/ui/UnitAssignmentList.tsx (1)
83-103:DraggableAssignmentItem의 props 타입을 명확히 정의하면 좋겠습니다.현재
Assignment전체 타입을 받지만 실제로는id와title만 사용합니다.Pick유틸리티 타입으로 필요한 속성만 명시하면 의도가 더 명확해집니다.♻️ 타입 명확화 제안
-const DraggableAssignmentItem = ({id, title}: Assignment) => { +interface DraggableAssignmentItemProps { + id: number; + title: string; +} + +const DraggableAssignmentItem = ({id, title}: DraggableAssignmentItemProps) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/unit-editor/ui/UnitAssignmentList.tsx` around lines 83 - 103, DraggableAssignmentItem currently types its props as the full Assignment type but only uses id and title; change the component signature to accept a narrowed props type using Pick<Assignment, 'id' | 'title'> (or define an explicit interface like { id: string; title: string }) and update the parameter typing in the DraggableAssignmentItem declaration so the intent is clear and excess fields are not required.src/shared/ui/button/Button.tsx (1)
7-7:typeprop에'reset'옵션 추가를 고려해보세요.HTML
<button>의type속성은'button' | 'submit' | 'reset'을 지원합니다. 폼 리셋 기능이 필요한 경우를 대비해 추가하면 유연성이 높아집니다.♻️ reset 타입 추가
- type?: 'button' | 'submit'; + type?: 'button' | 'submit' | 'reset';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/button/Button.tsx` at line 7, The Button component's type prop currently only allows 'button' | 'submit', missing the HTML 'reset' option; update the ButtonProps/type annotation to include 'reset' (e.g., change type?: 'button' | 'submit' to type?: 'button' | 'submit' | 'reset') and ensure any usages or default for the Button component (e.g., defaultProps or destructured default in Button) continue to work with the added 'reset' value.src/pages/unit-editor/ui/UnitList.tsx (2)
59-69:onAddNewUnit옵셔널 prop 호출 시 안전 처리 필요
onAddNewUnit이undefined일 수 있는데,Button의onClick에 직접 전달하고 있습니다. 현재 Button 컴포넌트가undefinedhandler를 안전하게 처리한다면 괜찮지만, 명시적으로 처리하는 것이 안전합니다.🛡️ 제안된 수정
<Button - onClick={onAddNewUnit} + onClick={onAddNewUnit ?? undefined} color='primary' size='compact' content='mixed'>또는 조건부 렌더링:
+ {onAddNewUnit && ( <div className='px-12 mt-4.5'> <Button onClick={onAddNewUnit} ... </Button> </div> + )}🤖 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 59 - 69, The onAddNewUnit prop may be undefined but is passed directly to Button.onClick; update the UnitList component to guard the call by passing a safe handler (e.g., an inline function that checks if onAddNewUnit exists before invoking) or conditionally render the Button only when onAddNewUnit is provided; locate the usage in UnitList.tsx where onAddNewUnit is passed to the Button component and wrap the call with a null-check around the onAddNewUnit prop (reference symbols: onAddNewUnit, Button, UnitList).
38-56: 리스트 아이템 접근성 개선 권장
<li>요소에onClick만 있고 키보드 접근성(onKeyDown,tabIndex,role)이 없습니다. 스크린 리더 사용자나 키보드 사용자를 위해 접근성 속성 추가를 고려해주세요.♿ 접근성 개선 제안
<li onClick={() => handleSelectUnit(id)} + onKeyDown={(e) => e.key === 'Enter' && handleSelectUnit(id)} + tabIndex={0} + role="button" key={id} className={`flex flex-col py-5 px-12 gap-2.5 cursor-pointer ${selectedUnitId === id ? 'bg-background' : ''}`}>참고: MDN - Keyboard-navigable JavaScript widgets
🤖 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 38 - 56, The list items in UnitList.tsx currently only handle mouse clicks, so update the <li> rendering (the element using handleSelectUnit, selectedUnitId, and ArrowrightIcon) to be keyboard-accessible by adding tabIndex={0}, role="button" and an onKeyDown handler that listens for Enter and Space and calls handleSelectUnit(id); also ensure the onClick remains, and include an appropriate aria-current or aria-pressed/aria-selected attribute (e.g., aria-current={selectedUnitId === id}) to expose selection state to assistive tech.src/pages/unit-editor/UnitEditorPage.tsx (2)
46-50:alert()대신 토스트 알림 사용 고려현재
alert()를 사용하고 있는데, 사용자 경험 향상을 위해 토스트 알림 라이브러리(예: react-hot-toast, sonner) 도입을 고려해보세요. 이는 나중에 개선해도 됩니다.Also applies to: 59-63, 74-78
🤖 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 46 - 50, Replace synchronous alert() calls in UnitEditorPage component with toast notifications from a chosen library (e.g., react-hot-toast or sonner): import the toast API at the top and replace alert('새 단원이 성공적으로 생성되었습니다.') with toast.success(...) and replace alert('단원 생성에 실패했습니다. 다시 시도해주세요.') and other alert calls with toast.error(...) (or toast()) in the relevant mutation/handler callbacks. Update the onSuccess and onError handlers referenced in UnitEditorPage (the create/update/delete mutation callbacks around the current onSuccess/onError blocks and the other occurrences noted at the 59-63 and 74-78 regions) to use toast, ensure imports are added, and keep the existing console.error logging for errors.
22-29:useEffect의존성 배열에서 잠재적 상태 갱신 루프 주의
mode를 의존성 배열에 포함하면서 내부에서setMode를 호출하고 있습니다. 현재mode === 'idle'조건으로 보호되어 있지만, 향후 로직 변경 시 무한 루프가 발생할 수 있습니다. 의도된 동작이라면 주석으로 명시해두면 좋겠습니다.🤖 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 22 - 29, The effect currently depends on both unitList and mode while calling setMode (inside useEffect), which risks a state-update loop; change the dependency array to only [unitList] so the effect runs when unitList changes and keep the internal guards (mode === 'idle') intact, or if you intentionally want mode in deps, add a clear comment above the useEffect explaining the intentional self-update and why it won't loop; update references to useEffect, setMode, mode, and setSelectedUnitId accordingly.src/entities/unit/api/unitQueries.ts (2)
13-17:getUnitDetails타입 안전성 개선 고려
enabled: !!unitId로 쿼리 실행을 방지하지만,queryFn내부에서unitId가null일 때의 타입 처리가 명시적이지 않습니다. 현재 구현은 동작하지만, 타입 단언을 추가하면 더 명확해집니다.♻️ 타입 안전성 개선 제안
getUnitDetails: (unitId: number | null) => queryOptions({ queryKey: ['units', 'detail', unitId], - queryFn: () => getUnitById(unitId), + queryFn: () => getUnitById(unitId!), enabled: !!unitId, }),🤖 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 13 - 17, The getUnitDetails query uses enabled: !!unitId but queryFn: () => getUnitById(unitId) still accepts unitId | null; update queryFn to be type-safe by handling null explicitly — e.g., change queryFn to () => { if (unitId == null) return Promise.reject(new Error('unitId is required')); return getUnitById(unitId); } or use a non-null assertion getUnitById(unitId!) if you guarantee enabled prevents null — modify getUnitDetails accordingly to reference getUnitById and unitId.
1-2: 상대 경로 대신 절대 경로 임포트 사용 권장프로젝트 코딩 가이드라인에 따라
@/...형식의 절대 경로 임포트를 사용하는 것이 좋습니다.♻️ 제안된 수정
import {queryOptions} from '@tanstack/react-query'; -import {getAllUnitsByCourseId, getUnitById} from './unitApi'; +import {getAllUnitsByCourseId, getUnitById} from '@/entities/unit/api/unitApi';As per coding guidelines: "절대 경로 임포트(
@/...) 사용"🤖 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 1 - 2, The current relative import of getAllUnitsByCourseId and getUnitById from './unitApi' should be converted to the project's absolute import style; update the import statement that brings in getAllUnitsByCourseId and getUnitById to use the '@/…' absolute path (e.g., import { getAllUnitsByCourseId, getUnitById } from '@/entities/unit/api/unitApi') so it follows the codebase convention while leaving the external library import (queryOptions from '@tanstack/react-query') unchanged.src/entities/unit/api/unitMutations.ts (1)
1-2: 상대 경로 대신 절대 경로 임포트 사용 권장일관성을 위해
@/절대 경로 임포트를 사용하세요.♻️ 제안된 수정
-import type {TUnitFormSchema} from '../model/types'; -import {createUnit, deleteUnit, updateUnit} from './unitApi'; +import type {TUnitFormSchema} from '@/entities/unit/model/types'; +import {createUnit, deleteUnit, updateUnit} from '@/entities/unit/api/unitApi';As per coding guidelines: "절대 경로 임포트(
@/...) 사용"🤖 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 1 - 2, Replace the relative imports with project absolute imports: change the import that currently brings in TUnitFormSchema and the import that brings in createUnit, updateUnit, deleteUnit from relative paths (e.g. './unitApi' and '../model/types') to use the '@/...' absolute import form; keep the imported symbols (TUnitFormSchema, createUnit, updateUnit, deleteUnit) unchanged, only update their module specifiers to the corresponding '@/...' paths to follow the codebase guideline.src/pages/unit-editor/ui/UnitForm.tsx (1)
10-10: 주석 처리된 코드 두 줄은 지금 정리해두면 더 깔끔합니다.사용하지 않는 주석(
useStateimport,assignmentIdsstate)은 추후 오해 포인트가 되기 쉬워요. 작은 정리지만 유지보수성에 꽤 도움 됩니다.Also applies to: 24-24
🤖 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` at line 10, Remove the dead/commented imports and state to keep UnitForm clean: delete the commented-out "import {useState} from 'react';" and the unused "assignmentIds" state declaration (the const [assignmentIds, ...] line) inside the UnitForm component so no unused/commented code remains; ensure there are no lingering references to assignmentIds in functions or JSX before committing.
🤖 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/unit/api/unitApi.ts`:
- Around line 15-19: getUnitById currently allows unitId to be null which
results in requests to "/units/null"; update getUnitById to guard against null
by validating unitId at the start of the function (in function getUnitById) and
immediately reject/throw a descriptive error (or return a failed
Promise<ApiResponse<Unit>>) when unitId is null/undefined, so the HTTP call to
privateAxios.get(`/units/${unitId}`) is never made; alternatively, if nullable
must be preserved by contract, narrow the type inside getUnitById (e.g., early
return when unitId == null) and document the behavior.
- Around line 31-36: The createUnit function is calling the wrong endpoint path;
update the POST in createUnit so it uses the same resource path as the list call
(i.e. POST to `/courses/${courseId}/units`) instead of `/units/${courseId}`;
modify the privateAxios.post call inside createUnit to target
`/courses/${courseId}/units` so create and list use a consistent RESTful
collection path.
In `@src/entities/unit/model/types.ts`:
- Around line 11-14: The current refine compares releaseDate and dueDate as
strings and allows invalid date strings; update the schema for releaseDate and
dueDate (the fields referenced in the .refine call) to validate/parse actual
Dates before comparison — e.g., replace z.string().min(1) with a Zod date
coercion or preprocessing (z.coerce.date() or z.preprocess to parse ISO strings)
or add a strict ISO regex check, then in the refine compare Date objects (or
check parsed validity with isNaN(date.getTime())) and return errors on the
appropriate path (dueDate/releaseDate) when parsing fails or releaseDate >
dueDate so the refine operates on validated dates rather than raw strings.
In `@src/features/auth/sync-user-role/model/useSyncUserRole.ts`:
- Around line 10-15: The current logic in useSyncUserRole that derives userType
from pathname and calls setUserType(userType) must be changed to avoid privilege
elevation: remove pathname-based assignment to userType and instead set userType
only from trusted authentication sources (e.g., the authenticated user object,
token validation, or an auth hook called getCurrentUser/getUserFromToken or
similar) inside the same hook; create a separate UI-only value routeRole (e.g.,
setRouteRole(routeRole)) derived from pathname for rendering decisions; ensure
functions/variables referenced are useSyncUserRole, setUserType, userType,
pathname and add setRouteRole/routeRole so route changes no longer overwrite the
trusted userType.
In `@src/index.css`:
- Around line 100-103: The global rule inside `@layer` base targeting label
(selector "label") forces every label to be flex-col and risks breaking
checkbox/radio layouts; narrow the scope by removing or replacing the global
"label" rule and applying these styles to a dedicated form-specific selector
(e.g., ".form-label" or ".field-label" or a container selector like ".form-row
label") instead, update the CSS to move the flex/gap declarations from the
global label to that class, and ensure corresponding HTML/JSX uses the new class
so only intended form labels receive flex-col and gap-[12.5px].
- Line 97: Enable Tailwind directive parsing in Biome so the `@apply` rule in
src/index.css is recognized: update the Biome config to set
css.parser.tailwindDirectives to true (alongside css.parser.cssModules if used)
so the `@apply` declarations (e.g., the rule containing "@apply bg-white w-[295px]
...") will be parsed and avoid noUnknownAtRule CI errors.
In `@src/pages/course-overview/ui/CourseActionsBar.tsx`:
- Line 13: The Link in CourseActionsBar currently hardcodes `/admin/units/3`;
update CourseActionsBar to use a dynamic id (e.g., unitId or courseId) instead
of the literal "3": retrieve the id from the component props or from React
Router (useParams) and replace the Link target with the template using that
variable (e.g., `/admin/units/${unitId}`), ensuring the prop or hook name you
use matches the component signature (CourseActionsBar) and any parent that
supplies the id.
In `@src/pages/dashboard/ui/CourseManagementDropdown.tsx`:
- Line 21: The "수정하기" option in CourseManagementDropdown currently navigates to
'courses/create' but course edit requires a proper route and an edit-capable
form; add a new route in App.tsx (e.g., 'courses/:id/edit') and change the
CourseManagementDropdown onClick to navigate to that route with the courseId,
then refactor CourseCreatePage into a combined create/edit component (follow the
AssignmentCreatePage pattern) by renaming title to '강의 개설 및 수정', using useParams
to read id to determine edit mode, loading existing course data when id is
present, and handling save/update accordingly so the edit flow works end-to-end.
In `@src/pages/unit-editor/ui/UnitAssignmentList.tsx`:
- Line 30: The component's local state assignments
(useState<Assignment[]>(assignmentList)) doesn't update when the assignmentList
prop changes; add a useEffect in UnitAssignmentList that listens to
assignmentList and calls setAssignments(assignmentList) to sync props to state
(keep existing assignments and setAssignments identifiers), ensuring initial
useState remains but subsequent prop updates overwrite the local assignments
state.
- Around line 42-47: getAssignmentIndex can close over stale assignments when
called inside the setAssignments functional updater; fix by using the updater's
current value instead of the outer helper: inside setAssignments((prev) => { ...
}) compute originalIndex and newIndex from prev (e.g. findIndex by active.id and
over.id) and then return arrayMove(prev, originalIndex, newIndex), referencing
setAssignments, getAssignmentIndex, arrayMove, active.id and over.id to locate
where to change.
In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Around line 49-56: The onSubmit handler currently falls back to creation when
mode === 'editing' but unit is missing; change the logic in onSubmit so that
when mode === 'editing' and unit is falsy it does NOT call onCreateUnit —
instead return early or invoke an error handler (e.g., set form error or call
onError) to prevent unintended creation; keep the existing behavior of calling
onUpdateUnit(unit.id, data) when unit exists and call onCreateUnit(data) only
when mode !== 'editing'.
- Line 132: The UnitAssignmentList component currently captures only the initial
assignmentList and doesn't resync internal state when switching units; fix this
by adding a React key to the UnitAssignmentList usage so it remounts on unit
change—specifically update the UnitForm render where UnitAssignmentList is used
to include key={unit.id} (using the unit.id unique identifier) so React will
unmount/mount a fresh instance and reset its internal state when the unit prop
changes.
In `@src/pages/unit-editor/ui/UnitList.tsx`:
- Around line 22-28: handleSelectUnit uses unitList?.findIndex but doesn't
handle the -1 result; change it to check if index === -1 and bail out (or
provide a clear fallback) before calling onChangeIndex so you don't pass an
incorrect value. Specifically, in handleSelectUnit, compute const index =
unitList?.findIndex(u => u.id === id); then if (index === -1 || index == null)
return (or set a deliberate fallback), otherwise call onChangeIndex(index + 1);
keep the onUnitClick(id) call as-is.
In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 14-19: The code converts id from useParams() into courseId with
Number(id) which can yield NaN and trigger unitQueries.getUnitList before id is
validated; update the logic in UnitEditorPage to guard the conversion or defer
the query by either (a) validate id first and only set courseId when id is
defined and a valid number, or (b) keep courseId as Number(id) but pass an
enabled option to useQuery for unitQueries.getUnitList(courseId) that requires
id to be present and a non-NaN number (e.g., enabled: !!id &&
!Number.isNaN(Number(id))); adjust any downstream uses of
courseId/selectedUnitId accordingly to account for the guarded/nullable state.
In `@src/shared/ui/badge/Badge.tsx`:
- Around line 18-20: Submission 배지에서 SubmissionBadgeVariants로부터 status가
optional로 오면서 props.status!로 non-null assertion을 쓰게 되어 런타임 크래시 위험이 있으니,
SubmissionBadgeProps의 타입을 변경하여 status를 필수로 강제하고 코드에서 non-null assertion을 제거하세요;
specifically, update the SubmissionBadgeProps definition (currently "type
SubmissionBadgeProps = { variant: 'submission'; } & SubmissionBadgeVariants;")
to require status (e.g. intersect or override with an explicit required status
type) so status is non-optional, then remove any uses of props.status! when
building SubmissionMeta and use props.status directly (refer to
SubmissionBadgeProps, SubmissionBadgeVariants, and SubmissionMeta to locate the
changes).
In `@src/shared/ui/button/Button.tsx`:
- Around line 15-33: The Button component is not forwarding onMouseEnter and
onMouseLeave from ButtonProps to the rendered <button>, so consumers' hover
handlers never fire; update the Button component (function Button) to include
onMouseEnter and onMouseLeave in the props passed to the <button> (e.g., extract
them from props or include ...props that contain them) so the handlers defined
in ButtonProps are forwarded to the DOM element while preserving existing
attributes like type, onClick, disabled, form, className, and other props.
In `@src/shared/ui/Layout.tsx`:
- Around line 12-15: In Layout.tsx replace the non‑standard Tailwind classes by
using arbitrary-value syntax: change any occurrences of "w-289" to "w-[289px]"
and "min-w-289" to "min-w-[289px]" (these appear on the outer wrapper div with
class 'min-h-screen overflow-x-hidden flex-center' and the inner
container/header divs where 'min-w-289' and 'w-289' are used), ensuring all
instances in the JSX className strings are updated to the bracketed pixel form.
In `@src/shared/ui/list-row/ListRow.tsx`:
- Line 1: Replace the relative import on the first line with the project's
absolute alias import: import { ListRowStyles, type ListRowVariants } from
'@/shared/ui/list-row/list-row-styles'; — update the import in ListRow.tsx to
use the '@/...' alias so the symbols ListRowStyles and ListRowVariants follow
the repository's absolute import convention.
---
Outside diff comments:
In `@src/pages/course-overview/ui/AssignmentList.tsx`:
- Around line 49-54: The Link in AssignmentList.tsx currently navigates to
`/assignments/${index}` which is unstable; update the component to use the
assignment's id instead by adding id to the props destructuring (in
AssignmentItemProps / the component that receives {...}) and replace
`/assignments/${index}` with `/assignments/${id}`; also confirm the route
`/assignments/:id` is defined in App.tsx so the new URL resolves correctly.
---
Nitpick comments:
In @.github/template/release-drafter.yml:
- Around line 5-23: The Release Drafter categories block is missing mappings for
standard commit tags (modify, add, remove, rename, perf, correct); update the
categories list so each missing tag is included under an appropriate
category—e.g., add 'add' and 'perf' to the '🚀 기능 추가' (or create a new '성능'
category for 'perf'), include 'modify', 'rename' and 'correct' under '♻️ 리팩토링'
or '🧰 그 외' as appropriate, and map 'remove' to '🐛 버그 수정' or '🧰 그 외' depending
on semantics—ensure the labels arrays in the categories block include these tag
strings so PRs using those commit tags are classified by Release Drafter (locate
and edit the categories section and the category titles '🚀 기능 추가', '♻️ 리팩토링',
'🐛 버그 수정', '🧰 그 외' to add the missing labels).
In @.github/workflows/ci-cd.yml:
- Around line 53-68: The workflow currently runs two build steps ("Build" which
runs `pnpm build` and "Simulate Vercel Build" which runs `npx vercel build`),
causing duplicate builds; decide which you want and remove the other step to
avoid redundant CI time. If you only need quick verification keep the `pnpm
build` step and delete the `npx vercel build` step (and its env block), or if
you need parity with Vercel keep `npx vercel build` and remove the `pnpm build`
step (and its env block); ensure remaining step preserves any required
environment variables (`VITE_KAKAO_REST_API_KEY`, `VITE_KAKAO_REDIRECT_URI`,
`VITE_API_BASE_URL`) and that the "Pull Vercel Environment" step remains if you
rely on those pulled envs.
- Around line 1-12: Add an explicit top-level permissions declaration in the
workflow (introduce the permissions key near the top alongside on/concurrency)
and set only the minimum GitHub Actions permissions required for this pipeline
(e.g., contents: read, actions: read, pull-requests: write, id-token: write only
if OIDC is used) instead of relying on defaults; update any steps that currently
assume broader access to use the narrowed permissions and remove any unnecessary
write rights. Ensure the new permissions block is placed above or next to the
existing concurrency/on sections so reviewers can find it easily.
In @.github/workflows/release-drafter.yml:
- Around line 26-33: The if condition on the "Publish Release" step currently
includes a redundant check of github.event_name == 'push'; remove that redundant
part so the step uses only the branch check (i.e., keep if: github.ref ==
'refs/heads/main') in the Publish Release step to simplify the condition; locate
the step by its name "Publish Release" and the existing if: line and update it
accordingly.
In `@src/entities/unit/api/unitMutations.ts`:
- Around line 1-2: Replace the relative imports with project absolute imports:
change the import that currently brings in TUnitFormSchema and the import that
brings in createUnit, updateUnit, deleteUnit from relative paths (e.g.
'./unitApi' and '../model/types') to use the '@/...' absolute import form; keep
the imported symbols (TUnitFormSchema, createUnit, updateUnit, deleteUnit)
unchanged, only update their module specifiers to the corresponding '@/...'
paths to follow the codebase guideline.
In `@src/entities/unit/api/unitQueries.ts`:
- Around line 13-17: The getUnitDetails query uses enabled: !!unitId but
queryFn: () => getUnitById(unitId) still accepts unitId | null; update queryFn
to be type-safe by handling null explicitly — e.g., change queryFn to () => { if
(unitId == null) return Promise.reject(new Error('unitId is required')); return
getUnitById(unitId); } or use a non-null assertion getUnitById(unitId!) if you
guarantee enabled prevents null — modify getUnitDetails accordingly to reference
getUnitById and unitId.
- Around line 1-2: The current relative import of getAllUnitsByCourseId and
getUnitById from './unitApi' should be converted to the project's absolute
import style; update the import statement that brings in getAllUnitsByCourseId
and getUnitById to use the '@/…' absolute path (e.g., import {
getAllUnitsByCourseId, getUnitById } from '@/entities/unit/api/unitApi') so it
follows the codebase convention while leaving the external library import
(queryOptions from '@tanstack/react-query') unchanged.
In `@src/index.css`:
- Around line 96-98: The .input-field utility currently uses a fixed width
(w-[295px]) which limits responsiveness; change it to use w-full combined with a
sensible max width (e.g., max-w-[295px] or an existing max-w token) so the rule
reads: keep bg-white, px-[14.6px], py-[10.5px], rounded-[9px], border-[0.9px]
border-purple-stroke and focus styles but replace w-[295px] with w-full and add
max-w-[295px] to allow full-width behavior on small screens while capping width
on larger ones.
- Line 31: The :root rule setting box-sizing: border-box doesn't propagate to
all elements/pseudo-elements; add a rule using the universal selectors to ensure
consistency by adding a selector block for *, *::before, *::after that sets
box-sizing: inherit (so all elements inherit the :root value). Update the CSS
near the existing :root/box-sizing declaration and target the universal
selectors to enforce border-box across elements and pseudo-elements.
In `@src/pages/admin/assignments/AssignmentCreatePage.tsx`:
- Around line 42-44: The map over examples in AssignmentCreatePage uses the loop
index (key={idx}) which will break identity if items are removed or reordered;
change the key to a stable unique identifier from the example object (e.g., use
example.id in the examples.map callback) or ensure a unique id is assigned when
examples are created (generate and persist a UUID) so replace key={idx} with
key={example.id} and update creation logic that produces examples to include
that id.
In `@src/pages/admin/courses/CourseCreatePage.tsx`:
- Around line 53-54: The empty inline handlers onCancel and onConfirm in the
CourseCreatePage component should be annotated with a clear TODO comment (or
replaced with small named placeholder handlers like handleCancel and
handleConfirm) indicating they are intentionally unimplemented until API
integration; update the JSX where onCancel={() => {}} and onConfirm={() => {}}
are declared to include a TODO comment referencing the expected future behavior
(e.g., "TODO: implement cancel behavior / call createCourse API") or call a
placeholder function that logs a warning so future work and reviewers can see
these are deliberate stubs.
In `@src/pages/unit-editor/ui/UnitAssignmentList.tsx`:
- Around line 83-103: DraggableAssignmentItem currently types its props as the
full Assignment type but only uses id and title; change the component signature
to accept a narrowed props type using Pick<Assignment, 'id' | 'title'> (or
define an explicit interface like { id: string; title: string }) and update the
parameter typing in the DraggableAssignmentItem declaration so the intent is
clear and excess fields are not required.
In `@src/pages/unit-editor/ui/UnitForm.tsx`:
- Line 10: Remove the dead/commented imports and state to keep UnitForm clean:
delete the commented-out "import {useState} from 'react';" and the unused
"assignmentIds" state declaration (the const [assignmentIds, ...] line) inside
the UnitForm component so no unused/commented code remains; ensure there are no
lingering references to assignmentIds in functions or JSX before committing.
In `@src/pages/unit-editor/ui/UnitList.tsx`:
- Around line 59-69: The onAddNewUnit prop may be undefined but is passed
directly to Button.onClick; update the UnitList component to guard the call by
passing a safe handler (e.g., an inline function that checks if onAddNewUnit
exists before invoking) or conditionally render the Button only when
onAddNewUnit is provided; locate the usage in UnitList.tsx where onAddNewUnit is
passed to the Button component and wrap the call with a null-check around the
onAddNewUnit prop (reference symbols: onAddNewUnit, Button, UnitList).
- Around line 38-56: The list items in UnitList.tsx currently only handle mouse
clicks, so update the <li> rendering (the element using handleSelectUnit,
selectedUnitId, and ArrowrightIcon) to be keyboard-accessible by adding
tabIndex={0}, role="button" and an onKeyDown handler that listens for Enter and
Space and calls handleSelectUnit(id); also ensure the onClick remains, and
include an appropriate aria-current or aria-pressed/aria-selected attribute
(e.g., aria-current={selectedUnitId === id}) to expose selection state to
assistive tech.
In `@src/pages/unit-editor/UnitEditorPage.tsx`:
- Around line 46-50: Replace synchronous alert() calls in UnitEditorPage
component with toast notifications from a chosen library (e.g., react-hot-toast
or sonner): import the toast API at the top and replace alert('새 단원이 성공적으로
생성되었습니다.') with toast.success(...) and replace alert('단원 생성에 실패했습니다. 다시
시도해주세요.') and other alert calls with toast.error(...) (or toast()) in the
relevant mutation/handler callbacks. Update the onSuccess and onError handlers
referenced in UnitEditorPage (the create/update/delete mutation callbacks around
the current onSuccess/onError blocks and the other occurrences noted at the
59-63 and 74-78 regions) to use toast, ensure imports are added, and keep the
existing console.error logging for errors.
- Around line 22-29: The effect currently depends on both unitList and mode
while calling setMode (inside useEffect), which risks a state-update loop;
change the dependency array to only [unitList] so the effect runs when unitList
changes and keep the internal guards (mode === 'idle') intact, or if you
intentionally want mode in deps, add a clear comment above the useEffect
explaining the intentional self-update and why it won't loop; update references
to useEffect, setMode, mode, and setSelectedUnitId accordingly.
In `@src/shared/ui/button/Button.tsx`:
- Line 7: The Button component's type prop currently only allows 'button' |
'submit', missing the HTML 'reset' option; update the ButtonProps/type
annotation to include 'reset' (e.g., change type?: 'button' | 'submit' to type?:
'button' | 'submit' | 'reset') and ensure any usages or default for the Button
component (e.g., defaultProps or destructured default in Button) continue to
work with the added 'reset' value.
ℹ️ Review info
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/assets/svg/addIcon.svgis excluded by!**/*.svgsrc/assets/svg/binIcon.svgis excluded by!**/*.svg
📒 Files selected for processing (83)
.github/template/release-drafter.yml.github/workflows/ci-cd.yml.github/workflows/release-drafter.ymlindex.htmlpackage.jsonsrc/App.tsxsrc/components/admin/assignments/AssignmentCard.tsxsrc/components/common/ActionButton.tsxsrc/components/common/IconButton.tsxsrc/components/common/SelectableItem.tsxsrc/entities/assignment/model/types.tssrc/entities/course/model/types.tssrc/entities/student/ui/AssignmentProgressCard.tsxsrc/entities/student/ui/StudentProfile.tsxsrc/entities/student/ui/StudentTable.tsxsrc/entities/unit/api/unitApi.tssrc/entities/unit/api/unitMutations.tssrc/entities/unit/api/unitQueries.tssrc/entities/unit/model/types.tssrc/features/auth/sync-user-role/model/useSyncUserRole.tssrc/features/course/filter-course/ui/CourseSelector.tsxsrc/hooks/useClickOutside.tssrc/index.csssrc/models/assignment.tssrc/models/course.tssrc/pages/admin/assignments/AssignmentCreatePage.tsxsrc/pages/admin/courses/CourseCreatePage.tsxsrc/pages/admin/student/StudentManagementPage.tsxsrc/pages/admin/student/StudentProfilePage.tsxsrc/pages/common/LandingPage.tsxsrc/pages/common/UserIdInputPage.tsxsrc/pages/course-overview/CourseOverviewPage.tsxsrc/pages/course-overview/models/types.tssrc/pages/course-overview/ui/AssignmentList.tsxsrc/pages/course-overview/ui/CourseActionsBar.tsxsrc/pages/course-overview/ui/CourseContent.tsxsrc/pages/course-overview/ui/CourseHero.tsxsrc/pages/course-overview/ui/UnitItem.tsxsrc/pages/dashboard/Dashboard.tsxsrc/pages/dashboard/models/types.tssrc/pages/dashboard/ui/CourseCard.tsxsrc/pages/dashboard/ui/CourseList.tsxsrc/pages/dashboard/ui/CourseManagementDropdown.tsxsrc/pages/dashboard/ui/ScheduleCard.tsxsrc/pages/dashboard/ui/ScheduleList.tsxsrc/pages/select-assignment/AssignmentSelectPage.tsxsrc/pages/unit-editor/UnitEditorPage.tsxsrc/pages/unit-editor/mock/unitMock.tssrc/pages/unit-editor/model/types.tssrc/pages/unit-editor/ui/UnitAssignmentList.tsxsrc/pages/unit-editor/ui/UnitForm.tsxsrc/pages/unit-editor/ui/UnitList.tsxsrc/shared/lib/course.tssrc/shared/mocks/assignmentDummyResponse.tssrc/shared/mocks/assignmentDummyTypes.tssrc/shared/mocks/assignmentSelectResponse.tssrc/shared/mocks/courseOverviewResponse.tssrc/shared/mocks/dashboardCourseList.tssrc/shared/mocks/dashboardScheduleList.tssrc/shared/model/common.tssrc/shared/ui/BaseHeader.tsxsrc/shared/ui/Dropdown.tsxsrc/shared/ui/EmptyState.tsxsrc/shared/ui/FileUpload.tsxsrc/shared/ui/Header.tsxsrc/shared/ui/Input.tsxsrc/shared/ui/LabeledDropdown.tsxsrc/shared/ui/LabeledInput.tsxsrc/shared/ui/Layout.tsxsrc/shared/ui/PrivateRoute.tsxsrc/shared/ui/ProgressIndicators.tsxsrc/shared/ui/SurfaceCard.tsxsrc/shared/ui/badge/Badge.tsxsrc/shared/ui/badge/badge-styles.tssrc/shared/ui/button/Button.tsxsrc/shared/ui/button/button-styles.tssrc/shared/ui/checkbox/Checkbox.module.csssrc/shared/ui/checkbox/Checkbox.tsxsrc/shared/ui/list-row/ListRow.tsxsrc/shared/ui/list-row/list-row-styles.tssrc/utils/course.tssrc/widgets/assignment-form-layout/ui/AssignmentFormLayout.tsxsrc/widgets/assignment-page-layout/ui/AssignmentPageLayout.tsx
💤 Files with no reviewable changes (12)
- index.html
- src/shared/ui/PrivateRoute.tsx
- src/components/common/IconButton.tsx
- src/components/common/ActionButton.tsx
- src/hooks/useClickOutside.ts
- src/components/common/SelectableItem.tsx
- src/pages/dashboard/models/types.ts
- src/utils/course.ts
- src/models/assignment.ts
- src/components/admin/assignments/AssignmentCard.tsx
- src/pages/course-overview/models/types.ts
- src/models/course.ts
| export const getUnitById = async ( | ||
| unitId: number | null | ||
| ): Promise<ApiResponse<Unit>> => { | ||
| const response = await privateAxios.get(`/units/${unitId}`); | ||
| return response.data; |
There was a problem hiding this comment.
null unitId가 그대로 API 경로로 들어가는 케이스를 차단해주세요.
Line [16]-Line [19]에서 unitId가 null이면 /units/null 요청이 발생합니다. 조회 실패를 런타임까지 미루지 말고 함수 레벨에서 막는 게 안전합니다.
참고: TypeScript null 안전성 가이드 — https://www.typescriptlang.org/docs/handbook/2/narrowing.html
수정 제안
-export const getUnitById = async (
- unitId: number | null
-): Promise<ApiResponse<Unit>> => {
+export const getUnitById = async (
+ unitId: number
+): Promise<ApiResponse<Unit>> => {
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};또는 호출부 계약상 nullable 유지가 필요하다면:
export const getUnitById = async (
unitId: number | null
): Promise<ApiResponse<Unit>> => {
+ if (unitId == null) {
+ throw new Error('unitId is required');
+ }
const response = await privateAxios.get(`/units/${unitId}`);
return response.data;
};📝 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.
| export const getUnitById = async ( | |
| unitId: number | null | |
| ): Promise<ApiResponse<Unit>> => { | |
| const response = await privateAxios.get(`/units/${unitId}`); | |
| return response.data; | |
| export const getUnitById = async ( | |
| unitId: number | |
| ): Promise<ApiResponse<Unit>> => { | |
| const response = await privateAxios.get(`/units/${unitId}`); | |
| return response.data; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/entities/unit/api/unitApi.ts` around lines 15 - 19, getUnitById currently
allows unitId to be null which results in requests to "/units/null"; update
getUnitById to guard against null by validating unitId at the start of the
function (in function getUnitById) and immediately reject/throw a descriptive
error (or return a failed Promise<ApiResponse<Unit>>) when unitId is
null/undefined, so the HTTP call to privateAxios.get(`/units/${unitId}`) is
never made; alternatively, if nullable must be preserved by contract, narrow the
type inside getUnitById (e.g., early return when unitId == null) and document
the behavior.
| export const createUnit = async ( | ||
| courseId: number, | ||
| unit: TUnitFormSchema | ||
| ): Promise<ApiResponse<Unit>> => { | ||
| const response = await privateAxios.post(`/units/${courseId}`, unit); | ||
| return response.data; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find all unit API endpoints
echo "=== Unit API endpoints ==="
rg -nP --type=ts "privateAxios\.(get|post|put|delete)\(" src/entities/unit -C2
echo -e "\n=== Course-Unit endpoint patterns ==="
rg -nP --type=ts "/courses/\$\{[^}]+\}/units|/units/\$\{[^}]+\}" src -C2
echo -e "\n=== All unit endpoint patterns ==="
rg -nP --type=ts "/units" src -C1Repository: 2025-snowCode/snowCode-Client
Length of output: 3317
생성 엔드포인트의 경로를 목록 조회와 일치시켜 주세요.
같은 파일에서 목록 조회는 GET /courses/${courseId}/units (라인 10)인데, 생성은 POST /units/${courseId} (라인 35)로 경로 패턴이 다릅니다. RESTful 컨벤션에 따르면 같은 리소스 컬렉션의 조회와 생성 작업은 동일한 경로를 사용해야 합니다.
제안: createUnit 함수의 엔드포인트를 /courses/${courseId}/units로 변경하여 일관성을 맞춰주세요. 현재 상태로는 백엔드 라우팅이 POST /units/${courseId} 를 지원하지 않으면 404/405 에러가 발생합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/entities/unit/api/unitApi.ts` around lines 31 - 36, The createUnit
function is calling the wrong endpoint path; update the POST in createUnit so it
uses the same resource path as the list call (i.e. POST to
`/courses/${courseId}/units`) instead of `/units/${courseId}`; modify the
privateAxios.post call inside createUnit to target `/courses/${courseId}/units`
so create and list use a consistent RESTful collection path.
| .refine((data) => data.releaseDate <= data.dueDate, { | ||
| message: '날짜 범위가 올바르지 않습니다.', | ||
| path: ['dueDate'], | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "types.ts" | grep -E "unit|entities" | head -20Repository: 2025-snowCode/snowCode-Client
Length of output: 262
🏁 Script executed:
cat -n src/entities/unit/model/types.tsRepository: 2025-snowCode/snowCode-Client
Length of output: 680
🏁 Script executed:
# Search for mock data or test files related to unit
find . -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "unitFormSchema\|releaseDate" | head -10Repository: 2025-snowCode/snowCode-Client
Length of output: 341
🌐 Web query:
Zod date validation best practices YYYY-MM-DD string comparison
💡 Result:
Zod best practices for validating YYYY-MM-DD date strings
-
Don’t rely on
z.string().date()unless you’ve confirmed your Zod version supports it
In Zod v3, people commonly hit “z.string().date is not a function” and userefine()/regexinstead. [1] -
Validate format and that it’s a real calendar date (not just “2024-02-31”)
A solid pattern is:
- enforce
YYYY-MM-DDwith a regex - then verify it round-trips via
Date→toISOString()(UTC) so invalid dates don’t slip through [1][2]
import { z } from "zod";
const YMD = z.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD")
.refine((s) => {
const d = new Date(s); // parsed as an ISO-like input
return !Number.isNaN(d.valueOf()) &&
d.toISOString().startsWith(s); // "YYYY-MM-DDTHH:mm:ss.sssZ" [3]
}, "Invalid date");- If you actually want a
Dateoutput, prefer coercion/pipelines
z.coerce.date()is the standard Zod approach to accept a string and produce aDate. [4]- You can combine “string shape” + “coerce to Date” with
.pipe()(nice for form/API inputs). [5]
const YMDAsDate = z.string()
.regex(/^\d{4}-\d{2}-\d{2}$/)
.pipe(z.coerce.date()); // output: Date [4][5]-
About “string comparison” (
"YYYY-MM-DD"sorting / min / max checks)
Lexicographic string comparison only works safely if you first guarantee the string is exactlyYYYY-MM-DDwith zero-padding (as above). After that,"2024-01-09" < "2024-10-01"behaves as expected.
Best practice is: validate first, then compare strings (or compare parsed Dates if you’re doing anything timezone-sensitive). -
If you really mean timestamps, use
z.string().datetime()instead
For ISO 8601 date-times, Zod hasz.string().datetime()(with options like allowing offsets). [2]
Sources: [1] [2] [3] [4] [5]
🏁 Script executed:
cat -n src/pages/unit-editor/mock/unitMock.ts | head -50Repository: 2025-snowCode/snowCode-Client
Length of output: 1333
🏁 Script executed:
cat -n src/pages/unit-editor/ui/UnitForm.tsx | grep -A 10 -B 10 "releaseDate\|dueDate" | head -40Repository: 2025-snowCode/snowCode-Client
Length of output: 1333
날짜 입력 형식 검증이 필수입니다.
현재 코드는 releaseDate와 dueDate를 단순히 z.string().min(1)로만 검증하고 있어, "2025-13-45" 같은 유효하지 않은 날짜 문자열도 통과합니다. 이 상태에서 문자열 비교(data.releaseDate <= data.dueDate)는 예상치 못한 결과를 초래할 수 있습니다.
해결 방법: Zod의 정규식 검증 또는 coerce.date()를 사용해 입력 형식을 먼저 검증하세요.
.object({
title: z.string().min(1, '단원 제목을 입력해주세요.'),
- releaseDate: z.string().min(1, '공개일을 입력해주세요.'),
- dueDate: z.string().min(1, '마감일을 입력해주세요.'),
+ releaseDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '날짜는 YYYY-MM-DD 형식이어야 합니다.'),
+ dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, '날짜는 YYYY-MM-DD 형식이어야 합니다.'),
assignmentIds: z.array(z.number()).optional(),
})참고: Zod 공식 문서 - refine & superRefine
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/entities/unit/model/types.ts` around lines 11 - 14, The current refine
compares releaseDate and dueDate as strings and allows invalid date strings;
update the schema for releaseDate and dueDate (the fields referenced in the
.refine call) to validate/parse actual Dates before comparison — e.g., replace
z.string().min(1) with a Zod date coercion or preprocessing (z.coerce.date() or
z.preprocess to parse ISO strings) or add a strict ISO regex check, then in the
refine compare Date objects (or check parsed validity with
isNaN(date.getTime())) and return errors on the appropriate path
(dueDate/releaseDate) when parsing fails or releaseDate > dueDate so the refine
operates on validated dates rather than raw strings.
| const userType = pathname.startsWith('/admin') | ||
| ? 'admin' | ||
| : pathname.startsWith('/student') | ||
| ? 'student' | ||
| : 'guest'; | ||
| setUserType(userType); |
There was a problem hiding this comment.
경로 기반 userType 덮어쓰기는 권한 상승 취약점입니다.
Line [10]~Line [15]에서 URL prefix만으로 admin/student를 설정하면, 사용자가 경로만 바꿔도 전역 권한 상태가 바뀝니다.
userType은 로그인/토큰 검증 결과(서버 신뢰원)로만 설정하고, 경로 파생 값은 별도 routeRole(UI 상태)로 분리하세요.
참고 문서:
- OWASP A01 Broken Access Control: https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- React Router 라우팅 가이드: https://reactrouter.com/start/library/routing
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/auth/sync-user-role/model/useSyncUserRole.ts` around lines 10 -
15, The current logic in useSyncUserRole that derives userType from pathname and
calls setUserType(userType) must be changed to avoid privilege elevation: remove
pathname-based assignment to userType and instead set userType only from trusted
authentication sources (e.g., the authenticated user object, token validation,
or an auth hook called getCurrentUser/getUserFromToken or similar) inside the
same hook; create a separate UI-only value routeRole (e.g.,
setRouteRole(routeRole)) derived from pathname for rendering decisions; ensure
functions/variables referenced are useSyncUserRole, setUserType, userType,
pathname and add setRouteRole/routeRole so route changes no longer overwrite the
trusted userType.
| } | ||
|
|
||
| .input-field { | ||
| @apply bg-white w-[295px] px-[14.6px] py-[10.5px] rounded-[9px] border-[0.9px] border-purple-stroke focus:outline-1 focus:outline-primary; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "=== 1) Finding Biome configuration files ==="
find . -maxdepth 3 -type f \( -name 'biome.json' -o -name 'biome.jsonc' \) 2>/dev/null || echo "No biome config found in root/subdirs"
echo -e "\n=== 2) Checking for Biome/Tailwind in package.json ==="
if [ -f "package.json" ]; then
echo "package.json found. Checking biome and tailwind versions..."
cat package.json | jq '.devDependencies, .dependencies' 2>/dev/null | grep -E "biome|tailwind" || echo "No biome/tailwind found"
fi
echo -e "\n=== 3) Checking src/index.css around lines 97 and 102 ==="
if [ -f "src/index.css" ]; then
echo "File src/index.css exists. Reading context around lines 97 and 102..."
sed -n '92,107p' src/index.css
fi
echo -e "\n=== 4) Looking for CSS-related linting config ==="
ls -la .biome* 2>/dev/null || echo "No .biome files"
find . -maxdepth 2 -type f \( -name '.prettierrc*' -o -name '.eslintrc*' \) 2>/dev/null || echo "No eslint/prettier config found"Repository: 2025-snowCode/snowCode-Client
Length of output: 912
🏁 Script executed:
#!/bin/bash
echo "=== Checking if Biome is installed and its configuration ==="
npm list `@biomejs/biome` 2>/dev/null | head -20 || echo "Biome not listed"
echo -e "\n=== Full content of biome.json if exists ==="
if [ -f "biome.json" ]; then
cat biome.json
fiRepository: 2025-snowCode/snowCode-Client
Length of output: 2001
🌐 Web query:
Biome CSS linter Tailwind CSS @apply support configuration
💡 Result:
To make Biome’s CSS linter/parser understand Tailwind directives like @apply (and avoid “unknown at-rule” errors), enable Tailwind directive parsing:
// biome.json
{
"css": {
"parser": {
"tailwindDirectives": true
}
}
}Biome documents css.parser.tailwindDirectives as enabling parsing for Tailwind-specific syntax including @apply. [1] Biome introduced this as an opt-in Tailwind (v4) parsing feature in v2.3. [2]
If you still want to lint regular CSS strictly but ignore unknown at-rules only in your Tailwind entry files, disable Biome’s noUnknownAtRule for those files via an override:
{
"overrides": [
{
"includes": ["**/global.css", "**/tailwind.css"],
"linter": {
"rules": {
"nursery": {
"noUnknownAtRule": "off"
}
}
}
}
]
}(The rule is lint/nursery/noUnknownAtRule.) [3]
Sources:
[1] Biome configuration reference (css.parser.tailwindDirectives) https://biomejs.dev/reference/configuration/
[2] Biome v2.3 blog (Tailwind support + config snippet) https://biomejs.dev/pt-br/blog/biome-v2-3/
[3] Biome linter rule noUnknownAtRule https://biomejs.dev/linter/rules/no-unknown-at-rule/
🌐 Web query:
Biome 2.4.4 CSS parser Tailwind directives
💡 Result:
Biome’s CSS parser will only understand Tailwind-specific at-rules (e.g. @tailwind, @apply, @utility, @variant, @theme, etc.) if you explicitly enable Tailwind-directive parsing.
Enable Tailwind directives (recommended)
biome.json
{
"css": {
"parser": {
"tailwindDirectives": true
}
}
}This option is documented as enabling parsing of Tailwind-specific syntax like @theme, @utility, and @apply. [1]
Enable from the CLI (useful for quick checks / CI)
biome check --css-parse-tailwind-directives=true file.css(Also works with biome lint, biome format, biome ci.) [2]
If you still see warnings on @tailwind
There’s a known/open bug where @tailwind can still trigger warnings even when css.parser.tailwindDirectives is enabled. [3]
Sources: [1] [2] [3]
biome.json에 Tailwind 지시어 파싱 활성화
@apply 문법을 인식하도록 CSS 파서를 설정하세요. css.parser.tailwindDirectives를 true로 설정하면 Biome이 Tailwind 전용 문법을 올바르게 파싱합니다.
{
"css": {
"parser": {
"cssModules": true,
"tailwindDirectives": true
}
}
}설정 없이는 CI 파이프라인에서 noUnknownAtRule 오류로 실패할 수 있습니다. Biome 공식 문서 참고.
🧰 Tools
🪛 Biome (2.4.4)
[error] 97-97: Tailwind-specific syntax is disabled.
(parse)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/index.css` at line 97, Enable Tailwind directive parsing in Biome so the
`@apply` rule in src/index.css is recognized: update the Biome config to set
css.parser.tailwindDirectives to true (alongside css.parser.cssModules if used)
so the `@apply` declarations (e.g., the rule containing "@apply bg-white w-[295px]
...") will be parsed and avoid noUnknownAtRule CI errors.
| const {id} = useParams(); // 강의 ID | ||
| const courseId = Number(id); | ||
| const [mode, setMode] = useState<Mode>('idle'); | ||
| const [selectedUnitId, setSelectedUnitId] = useState<number | null>(null); | ||
| const [currentIndex, setCurrentIndex] = useState<number>(1); | ||
| const {data: unitList} = useQuery(unitQueries.getUnitList(courseId)); |
There was a problem hiding this comment.
courseId 변환 시 NaN 방어 로직 필요
id가 undefined일 때 Number(id)는 NaN을 반환합니다. 현재 !id 체크는 Line 113에서 이루어지지만, 그 전에 이미 NaN을 사용한 쿼리가 실행됩니다. 쿼리 실행 전에 유효성 검사를 추가하거나, enabled 옵션을 활용하는 것이 좋습니다.
🛡️ 제안된 수정
const UnitEditorPage = () => {
const {id} = useParams(); // 강의 ID
const courseId = Number(id);
+ const isValidCourseId = !isNaN(courseId) && courseId > 0;
const [mode, setMode] = useState<Mode>('idle');
const [selectedUnitId, setSelectedUnitId] = useState<number | null>(null);
const [currentIndex, setCurrentIndex] = useState<number>(1);
- const {data: unitList} = useQuery(unitQueries.getUnitList(courseId));
+ const {data: unitList} = useQuery({
+ ...unitQueries.getUnitList(courseId),
+ enabled: isValidCourseId,
+ });
const {data: unit} = useQuery(unitQueries.getUnitDetails(selectedUnitId));🤖 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 14 - 19, The code
converts id from useParams() into courseId with Number(id) which can yield NaN
and trigger unitQueries.getUnitList before id is validated; update the logic in
UnitEditorPage to guard the conversion or defer the query by either (a) validate
id first and only set courseId when id is defined and a valid number, or (b)
keep courseId as Number(id) but pass an enabled option to useQuery for
unitQueries.getUnitList(courseId) that requires id to be present and a non-NaN
number (e.g., enabled: !!id && !Number.isNaN(Number(id))); adjust any downstream
uses of courseId/selectedUnitId accordingly to account for the guarded/nullable
state.
| type SubmissionBadgeProps = { | ||
| variant: 'submission'; | ||
| } & VariantProps<typeof submissionBadgeStyles>; | ||
| } & SubmissionBadgeVariants; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat src/shared/ui/badge/Badge.tsxRepository: 2025-snowCode/snowCode-Client
Length of output: 1937
🏁 Script executed:
# SubmissionBadgeVariants 타입 정의 찾기
rg "type SubmissionBadgeVariants" -A 10Repository: 2025-snowCode/snowCode-Client
Length of output: 957
🏁 Script executed:
# 'submission' 배지 사용처 확인
rg "variant=['\"]submission['\"]" -B 2 -A 2Repository: 2025-snowCode/snowCode-Client
Length of output: 481
🏁 Script executed:
cat src/shared/ui/badge/badge-styles.tsRepository: 2025-snowCode/snowCode-Client
Length of output: 1354
🏁 Script executed:
# 더 포괄적으로 submission 배지 모든 사용 찾기
rg "Badge" src -A 5 -B 5 | grep -A 5 -B 5 "submission"Repository: 2025-snowCode/snowCode-Client
Length of output: 4197
🏁 Script executed:
# 모든 Badge 임포트 및 사용처를 더 넓게 검색
rg "from.*Badge" -A 20 | grep -E "(from.*Badge|variant=|submission)" -A 3 -B 3Repository: 2025-snowCode/snowCode-Client
Length of output: 2080
🏁 Script executed:
# JSX에서 <Badge 패턴 직접 찾기
rg "<Badge" -A 5Repository: 2025-snowCode/snowCode-Client
Length of output: 1793
submission 배지에서 타입 안전성 강화가 필요합니다.
현재 SubmissionBadgeVariants에서 status 속성이 optional로 추출되어, SubmissionBadgeProps의 status도 optional입니다. 그럼에도 props.status!로 단언하고 SubmissionMeta 객체에 접근하면 누락된 경우 런타임 크래시가 발생합니다.
타입 정의 단계에서 status를 필수로 강제하면 이런 실수를 컴파일 타임에 방지할 수 있습니다.
수정 예시(diff)
type SubmissionBadgeProps = {
variant: 'submission';
-} & SubmissionBadgeVariants;
+ status: NonNullable<SubmissionBadgeVariants['status']>;
+};그러면 사용 시 non-null assertion 제거:
-const {label, icon} = SubmissionMeta[props.status!];
+const {label, icon} = SubmissionMeta[props.status];참고: tailwind-variants TypeScript 가이드
📝 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.
| type SubmissionBadgeProps = { | |
| variant: 'submission'; | |
| } & VariantProps<typeof submissionBadgeStyles>; | |
| } & SubmissionBadgeVariants; | |
| type SubmissionBadgeProps = { | |
| variant: 'submission'; | |
| status: NonNullable<SubmissionBadgeVariants['status']>; | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/badge/Badge.tsx` around lines 18 - 20, Submission 배지에서
SubmissionBadgeVariants로부터 status가 optional로 오면서 props.status!로 non-null
assertion을 쓰게 되어 런타임 크래시 위험이 있으니, SubmissionBadgeProps의 타입을 변경하여 status를 필수로
강제하고 코드에서 non-null assertion을 제거하세요; specifically, update the
SubmissionBadgeProps definition (currently "type SubmissionBadgeProps = {
variant: 'submission'; } & SubmissionBadgeVariants;") to require status (e.g.
intersect or override with an explicit required status type) so status is
non-optional, then remove any uses of props.status! when building SubmissionMeta
and use props.status directly (refer to SubmissionBadgeProps,
SubmissionBadgeVariants, and SubmissionMeta to locate the changes).
| const Button = ({ | ||
| children, | ||
| onClick, | ||
| type = 'button', | ||
| disabled = false, | ||
| formID, | ||
| className, | ||
| ...props | ||
| }: ButtonProps) => { | ||
| return ( | ||
| <button | ||
| type={type} | ||
| onClick={onClick} | ||
| disabled={disabled} | ||
| form={formID} | ||
| className={twMerge(buttonStyles(props), className)}> | ||
| {children} | ||
| </button> | ||
| ); |
There was a problem hiding this comment.
onMouseEnter, onMouseLeave 핸들러가 버튼 요소에 전달되지 않습니다.
ButtonProps 인터페이스에 onMouseEnter와 onMouseLeave가 정의되어 있지만, 실제 <button> 요소에는 전달되지 않아 소비자가 이 핸들러를 전달해도 동작하지 않습니다.
🔧 누락된 핸들러 전달
const Button = ({
children,
onClick,
type = 'button',
disabled = false,
formID,
className,
+ onMouseEnter,
+ onMouseLeave,
...props
}: ButtonProps) => {
return (
<button
type={type}
onClick={onClick}
disabled={disabled}
form={formID}
+ onMouseEnter={onMouseEnter}
+ onMouseLeave={onMouseLeave}
className={twMerge(buttonStyles(props), className)}>
{children}
</button>
);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const Button = ({ | |
| children, | |
| onClick, | |
| type = 'button', | |
| disabled = false, | |
| formID, | |
| className, | |
| ...props | |
| }: ButtonProps) => { | |
| return ( | |
| <button | |
| type={type} | |
| onClick={onClick} | |
| disabled={disabled} | |
| form={formID} | |
| className={twMerge(buttonStyles(props), className)}> | |
| {children} | |
| </button> | |
| ); | |
| const Button = ({ | |
| children, | |
| onClick, | |
| type = 'button', | |
| disabled = false, | |
| formID, | |
| className, | |
| onMouseEnter, | |
| onMouseLeave, | |
| ...props | |
| }: ButtonProps) => { | |
| return ( | |
| <button | |
| type={type} | |
| onClick={onClick} | |
| disabled={disabled} | |
| form={formID} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| className={twMerge(buttonStyles(props), className)}> | |
| {children} | |
| </button> | |
| ); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/button/Button.tsx` around lines 15 - 33, The Button component
is not forwarding onMouseEnter and onMouseLeave from ButtonProps to the rendered
<button>, so consumers' hover handlers never fire; update the Button component
(function Button) to include onMouseEnter and onMouseLeave in the props passed
to the <button> (e.g., extract them from props or include ...props that contain
them) so the handlers defined in ButtonProps are forwarded to the DOM element
while preserving existing attributes like type, onClick, disabled, form,
className, and other props.
| <div className='min-h-screen overflow-x-hidden flex-center'> | ||
| <div className='min-w-289 px-4 py-12'> | ||
| {showHeader && ( | ||
| <div className='pb-8'> | ||
| <div className='w-289 pb-8 mx-auto'> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# tailwind.config 파일 찾기
fd -e "js" -e "ts" -e "json" "tailwind"Repository: 2025-snowCode/snowCode-Client
Length of output: 55
🏁 Script executed:
# Layout.tsx 파일 확인
fd -e "tsx" "Layout"Repository: 2025-snowCode/snowCode-Client
Length of output: 223
🏁 Script executed:
# w-289와 min-w-289 클래스 정의 검색
rg "w-289|min-w-289" -t css -t typescript -t javascriptRepository: 2025-snowCode/snowCode-Client
Length of output: 107
🏁 Script executed:
# Tailwind 테마에서 spacing 값 정의 확인
rg "289|flex-center" --type-add "config:*.{config.js,config.ts,tailwind.config.js,tailwind.config.ts}"Repository: 2025-snowCode/snowCode-Client
Length of output: 7362
🏁 Script executed:
# Layout.tsx 파일 내용 확인
cat -n src/shared/ui/Layout.tsxRepository: 2025-snowCode/snowCode-Client
Length of output: 903
🏁 Script executed:
# tailwind.config 파일 검색
fd "tailwind.config" -e ts -e jsRepository: 2025-snowCode/snowCode-Client
Length of output: 55
🏁 Script executed:
# src/index.css에서 커스텀 유틸리티 및 테마 확인
cat -n src/index.css | head -100Repository: 2025-snowCode/snowCode-Client
Length of output: 3137
🏁 Script executed:
# Search for all files that might contain tailwind configuration
find . -name "*.config.*" -o -name "tailwind*" 2>/dev/null | head -20Repository: 2025-snowCode/snowCode-Client
Length of output: 132
🏁 Script executed:
# Check if spacing values are defined in src/index.css beyond line 100
cat -n src/index.css | grep -E "(spacing|w-|min-w-|289|extend)" | head -30Repository: 2025-snowCode/snowCode-Client
Length of output: 471
🏁 Script executed:
# Verify that w-289 and min-w-289 are not defined anywhere
rg "w-289|min-w-289" --type css -B 2 -A 2Repository: 2025-snowCode/snowCode-Client
Length of output: 55
w-289와 min-w-289 클래스를 Tailwind 임의 값 문법으로 수정하세요.
w-289와 min-w-289는 프로젝트의 Tailwind 테마에 정의되어 있지 않습니다. 현재 코드베이스의 다른 폭 값들(예: w-[295px])처럼 명시적인 픽셀 단위 문법을 사용해주세요:
min-w-289→min-w-[289px]w-289→w-[289px]
Tailwind CSS v4에서 명시적 단위를 포함한 임의 값은 의도를 명확하게 하고 유지보수를 용이하게 합니다. Tailwind 임의 값 문서를 참고하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/Layout.tsx` around lines 12 - 15, In Layout.tsx replace the
non‑standard Tailwind classes by using arbitrary-value syntax: change any
occurrences of "w-289" to "w-[289px]" and "min-w-289" to "min-w-[289px]" (these
appear on the outer wrapper div with class 'min-h-screen overflow-x-hidden
flex-center' and the inner container/header divs where 'min-w-289' and 'w-289'
are used), ensuring all instances in the JSX className strings are updated to
the bracketed pixel form.
| @@ -0,0 +1,27 @@ | |||
| import {ListRowStyles, type ListRowVariants} from './list-row-styles'; | |||
There was a problem hiding this comment.
절대 경로 import 규칙으로 맞춰주세요.
Line 1의 상대 경로 import는 현재 저장소 규칙과 불일치합니다. 동일 레이어 리팩터링 시 경로 일관성을 위해 alias import로 통일하는 편이 안전합니다.
🔧 제안 수정안
-import {ListRowStyles, type ListRowVariants} from './list-row-styles';
+import {ListRowStyles, type ListRowVariants} from '@/shared/ui/list-row/list-row-styles';참고 문서:
- TypeScript
paths: https://www.typescriptlang.org/tsconfig#paths - Vite
resolve.alias: https://vite.dev/config/shared-options.html#resolve-alias
As per coding guidelines 절대 경로 임포트(@/...) 사용.
📝 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.
| import {ListRowStyles, type ListRowVariants} from './list-row-styles'; | |
| import {ListRowStyles, type ListRowVariants} from '@/shared/ui/list-row/list-row-styles'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/shared/ui/list-row/ListRow.tsx` at line 1, Replace the relative import on
the first line with the project's absolute alias import: import { ListRowStyles,
type ListRowVariants } from '@/shared/ui/list-row/list-row-styles'; — update the
import in ListRow.tsx to use the '@/...' alias so the symbols ListRowStyles and
ListRowVariants follow the repository's absolute import convention.
📦 전체 변경 이력
~ v0.1.0