Skip to content

Create Week9 Mission1~3#80

Open
mint0326 wants to merge 5 commits into
mainfrom
mint0326/week09
Open

Create Week9 Mission1~3#80
mint0326 wants to merge 5 commits into
mainfrom
mint0326/week09

Conversation

@mint0326
Copy link
Copy Markdown
Contributor

@mint0326 mint0326 commented May 26, 2026

📝 미션 번호

8주차 Misson 1~3

📋 구현 사항

Mission1

  • Redux Toolkit 활용 UMC Play List 제작
    - tailwind 스타일링
    - mock data 사용
    - redux store 생성
    - cartslice 설계

Mission2

  • Modal Slice 활용하여, 모달 기능 추가
  • modal slice 제작
  • 기존 clearCart action 활용
  • useState 대신 Reducer 사용

Mission3

  • Redux Toolkit으로 만든 UMC Play List를 Zustand로 리팩토링
  • Zutstand 스토어 사용
  • 모달, 액션 등 모두 Zutstand로 변경.

📎 스크린샷

Mission1

2026-05-27.003649_1.mp4

Mission2

2026-05-27.011355.mp4

Mission3

2026-05-28.015134.mp4

✅ 체크리스트

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

🤔 질문 사항

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 장바구니 UI 전면 추가: 항목 목록, 개별 증감/제거, 합계 및 총 수량 표시
    • 장바구니 전체 삭제 확인 모달 제공
    • 상단 네비게이션에 장바구니 아이콘과 수량 배지 실시간 표시
  • Documentation

    • React + TypeScript + Vite 템플릿 사용 가이드 및 ESLint 설정 예시 문서 추가
  • Chores

    • 개발환경 구성(타입스크립트, ESLint, Tailwind) 및 .gitignore 설정 추가

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

개요

Week09/mint0326 디렉토리에 두 개의 완전히 독립적인 Vite + React + TypeScript + Redux 플레이리스트 장바구니 애플리케이션이 추가되었습니다. mission1은 기본 기능을, mission2는 장바구니 삭제 시 확인 모달을 포함합니다.


Walkthrough

mission1/mission2/mission3에 각기 독립된 Vite+React 앱 템플릿과 스타일, 상태관리(cart 슬라이스 또는 Zustand), 컴포넌트(Navbar/CartContainer/CartListItem/Modal), 빌드·린트·타입 설정이 추가되었습니다.

Changes

Mission1: Redux 장바구니 애플리케이션

Layer / File(s) 요약
프로젝트 설정 및 환경 구성
Week09/mint0326/mission1/.gitignore, README.md, eslint.config.js, index.html, package.json, tsconfig.*, vite.config.ts
TypeScript 컴파일러 옵션(es2023 타깃, bundler 모드), Vite 빌드 설정(React 및 TailwindCSS 플러그인), ESLint Flat Config(tsx 린팅 규칙), npm 스크립트 및 의존성, HTML 엔트리 포인트를 완성합니다.
전역 스타일 및 UI 레이아웃
src/index.css, src/App.css
TailwindCSS 지시문과 기본 body 스타일, 그리고 카운터 버튼, 3D 변환 히어로 섹션, 반응형 네비게이션 및 문서 영역(1024px 미디어쿼리 포함)을 CSS로 정의합니다.
Redux 상태 관리 및 데이터 정의
src/store/store.ts, src/store/reducer/cartSlice.ts, src/constants/cartItems.ts
CartState 인터페이스와 초기값, clearCart/removeItem/increase/decrease/calculateTotals 리듀서를 포함한 Redux Toolkit 슬라이스, 그리고 타입 안전한 장바구니 아이템 인터페이스 및 정적 데이터 배열을 제공합니다.
애플리케이션 컴포넌트 및 부트스트랩
src/App.tsx, src/main.tsx, src/components/Navbar.tsx, src/components/CartContainer.tsx, src/components/CartListItem.tsx
App 컴포넌트가 Redux의 cartItems 변화 시 calculateTotals()를 디스패치하고, Navbar(ShoppingCart 배지), CartContainer(아이템 목록 및 합계), CartListItem(수량 조절)을 렌더링합니다. main.tsxStrictMode와 Redux Provider로 앱을 부트스트랩합니다.

Mission2: Redux 장바구니 애플리케이션 (모달 기능 추가)

Layer / File(s) 요약
프로젝트 설정 및 환경 구성
Week09/mint0326/mission2/.gitignore, README.md, eslint.config.js, index.html, package.json, tsconfig.*, vite.config.ts
Mission1과 동일한 설정 구조이며, package.json의 build 스크립트에 tsc 타입 체크가 추가됩니다.
전역 스타일 및 UI 레이아웃
src/index.css, src/App.css
Mission1과 동일한 TailwindCSS 및 반응형 CSS 구성입니다.
Redux 상태 관리 및 데이터 정의
src/store/store.ts, src/features/cart/cartSlice.ts, src/features/modal/modalSlice.ts, src/constants/cartItems.ts
Cart 슬라이스와 함께 모달 상태 관리(isOpen 토글)를 위한 ModalState 인터페이스과 openModal/closeModal 리듀서를 추가로 포함합니다. 스토어는 두 슬라이스를 결합합니다.
애플리케이션 컴포넌트 및 부트스트랩
src/App.tsx, src/main.tsx, src/components/Navbar.tsx, src/components/CartContainer.tsx, src/components/CartListItem.tsx, src/components/Modal.tsx
Mission1의 구조에 Modal 컴포넌트를 추가합니다. "장바구니 전체 삭제" 버튼 클릭 시 openModal()을 디스패치하고, 모달의 "네" 버튼은 clearCart()closeModal()을 순차 디스패치합니다. App 컴포넌트는 모달 상태에 따라 조건부로 렌더링합니다.

Mission3: Zustand 기반 장바구니 샘플

Layer / File(s) 요약
프로젝트 설정 및 환경 구성
Week09/mint0326/mission3/.gitignore, README.md, eslint.config.js, index.html, package.json, tsconfig.*, vite.config.ts
Vite/TypeScript/ESLint 설정과 패키지 메타데이터 및 HTML 엔트리 포인트를 추가합니다.
전역 스타일 및 UI 레이아웃
src/index.css, src/App.css
Tailwind import 및 전역 스타일과 App.css의 반응형 레이아웃을 포함합니다.
Zustand 스토어 및 데이터
src/store/useStore.ts, src/constants/cartItems.ts
Zustand로 useStore를 구현하고, cartItems 정적 데이터와 clear/remove/increase/decrease/calculateTotals/openModal/closeModal 액션을 제공합니다.
애플리케이션 컴포넌트 및 부트스트랩
src/App.tsx, src/main.tsx, src/components/*
App이 useStore를 사용해 합계 재계산과 모달 조건부 렌더링을 수행하고, Navbar/CartContainer/CartListItem/Modal 컴포넌트를 포함합니다.

추정 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~25 minutes

관련 가능성이 있는 PR

  • HSU-Makeus-Challenge-10th/Web#75: Week9 미션의 Redux 기반 cart 슬라이스 및 장바구니/모달 컴포넌트 구현과 코드 레벨에서 유사한 변경을 포함합니다.

🐰 장바구니 둘이 생겼네,
Redux로 상태 관리하고,
미션 하나는 모달까지! ✨
수량 증감, 삭제 기능,
플레이리스트 완성! 🎵

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 'Create Week9 Mission1~3'은 변경 사항의 주요 내용을 명확하게 설명하며, 추가된 Week 9의 3가지 미션을 모두 포함합니다.
Description check ✅ Passed PR 설명이 템플릿 구조를 따르고 있으며, 미션 번호, 구현 사항, 스크린샷, 체크리스트를 모두 포함하고 있습니다. 다만 미션 번호가 '8주차'로 기재되어 제목과 불일치합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mint0326/week09

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@mint0326 mint0326 self-assigned this May 26, 2026
Copy link
Copy Markdown

@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: 8

🧹 Nitpick comments (3)
Week09/mint0326/mission1/src/constants/cartItems.ts (1)

1-7: ⚡ Quick win

price를 문자열이 아니라 숫자 타입으로 고정하는 게 안전합니다.

가격을 문자열로 두면 계산 시 매번 파싱해야 하고, 포맷 문자열 유입 시 합계가 NaN이 될 수 있습니다. 데이터 모델에서 number로 고정해 주세요.

리팩터 예시
 export interface CartItemType {
   id: string;
   title: string;
   singer: string;
-  price: string;
+  price: number;
   img: string;
   amount: number;
 }

 const cartItems: CartItemType[] = [
   {
@@
-    price: '25000',
+    price: 25000,
@@
-    price: '18000',
+    price: 18000,
@@
-    price: '28000',
+    price: 28000,

(동일 패턴으로 나머지 항목도 숫자 리터럴로 통일)

Also applies to: 15-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/constants/cartItems.ts` around lines 1 - 7, The
CartItemType interface currently types price as string which risks parsing bugs
and NaN; change the price property to number in the CartItemType declaration and
update any related interfaces/usages (e.g., other definitions referenced by
"15-103") so numeric operations work without parsing, and audit places that
construct CartItemType or read price to remove string parsing/formatting
(convert incoming string values to numbers at the boundary or during mapping)
and ensure formatting for display happens at render time rather than in the data
model.
Week09/mint0326/mission2/src/constants/cartItems.ts (1)

1-7: ⚡ Quick win

price는 문자열 대신 숫자 타입으로 모델링하는 편이 안전합니다.

현재 구조는 합계 계산 시 매번 형변환이 필요하고, 비숫자 문자열 유입 시 NaN 위험을 타입 단계에서 막지 못합니다.

♻️ 제안 수정안
 export interface CartItemType {
   id: string;
   title: string;
   singer: string;
-  price: string;
+  price: number;
   img: string;
   amount: number;
 }
-    price: '25000',
+    price: 25000,

price 리터럴은 나머지 항목들도 동일하게 숫자로 바꿔주세요.

Also applies to: 15-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission2/src/constants/cartItems.ts` around lines 1 - 7, The
CartItemType interface models price as a string which forces repeated parsing
and allows invalid non-numeric values; change the price field to a number
(CartItemType.price: number) and update any literal cart item objects (where
amounts like "price" strings are used) to numeric values so downstream sum/total
calculations use numbers directly; verify places that construct or parse
CartItemType (e.g., any cart item arrays or creators referenced alongside
CartItemType) are updated to pass numbers not strings.
Week09/mint0326/mission2/src/components/CartContainer.tsx (1)

1-8: ⚡ Quick win

프로젝트 공용 typed Redux 훅(useAppDispatch/useAppSelector)으로 useDispatch/useSelector 사용을 통일
Week09/mint0326/mission2에서 react-reduxuseDispatch/useSelector를 컴포넌트별로 직접 쓰며(예: CartContainer.tsx, App.tsx, Navbar.tsx, Modal.tsx, CartListItem.tsx) RootState 제네릭도 반복되고 있습니다. 공용 typed 훅을 만들어 dispatch/상태 타입을 일관되게 고정한 뒤 관련 호출들을 useAppDispatch/useAppSelector로 교체해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission2/src/components/CartContainer.tsx` around lines 1 -
8, Replace direct react-redux hooks in CartContainer with the project-wide typed
hooks: stop using useDispatch/useSelector and the RootState generic here; import
and use useAppDispatch and useAppSelector instead. Concretely, update the
imports to pull useAppDispatch and useAppSelector, call const dispatch =
useAppDispatch() and const { cartItems, total, amount } = useAppSelector(state
=> state.cart), and leave usages like dispatch(openModal(...)) and CartListItem
unchanged. Ensure the typed hooks are already defined in your store hooks file
and reference those same hook names across other components as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Week09/mint0326/mission1/src/components/CartContainer.tsx`:
- Line 7: The CartContainer.tsx uses useDispatch() without typing; import the
AppDispatch type exported from store.ts and change the declaration to
useDispatch<AppDispatch>() (or add and use a typed helper hook named
useAppDispatch/useAppSelector across the app) so dispatch is strongly typed;
update the import in CartContainer.tsx to reference AppDispatch from store.ts
and replace the const dispatch = useDispatch() with the typed variant (or call
useAppDispatch()) to ensure consistent typed Redux Toolkit dispatch usage.

In `@Week09/mint0326/mission1/src/components/CartListItem.tsx`:
- Around line 19-37: The decrease/increase buttons in CartListItem render only
"+" and "-" and need descriptive aria-labels; update the two button elements
(the one whose onClick calls removeItem/decrease and the one that calls
increase) to include aria-label attributes that identify the product and action
(e.g., "Decrease quantity for {nameOrId}" and "Increase quantity for
{nameOrId}"), using the component's id prop or a name prop if available so
screen readers know which item's quantity is being adjusted.

In `@Week09/mint0326/mission1/src/components/Navbar.tsx`:
- Around line 11-15: The cart quantity badge lacks accessible labeling for
screen readers; update the container wrapping the ShoppingCart icon and badge
(the div with className "relative flex items-center") to include a descriptive
aria-label like "Cart, {amount} items" or similar, and mark the ShoppingCart
icon component with aria-hidden="true" so assistive tech reads only the
badge/label; ensure the dynamic amount value (amount) remains visible to sighted
users and is reflected in the aria-label text.

In `@Week09/mint0326/mission1/src/index.css`:
- Around line 5-6: font-family 선언에서 단일 단어 폰트명에 불필요한 따옴표가 있어 Stylelint 오류가 발생하므로
index.css의 font-family 속성에서 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell' 같은 단어
폰트명에서 따옴표를 제거하고 복합명(예: 'Segoe UI', 'Helvetica Neue')만 따옴표로 유지하도록 수정하세요.

In `@Week09/mint0326/mission1/src/store/reducer/cartSlice.ts`:
- Around line 33-37: The decrease reducer currently allows cartItem.amount to go
below zero; update the decrease handler in cartSlice (decrease) to prevent
negative amounts by either clamping cartItem.amount = Math.max(0,
cartItem.amount - 1) or by removing the item from state.cartItems when
cartItem.amount would drop below 1 (i.e., if cartItem.amount <= 1 then remove
that item else decrement); reference the decrease reducer and
state.cartItems/cartItem.amount when applying the change.

In `@Week09/mint0326/mission2/src/components/Modal.tsx`:
- Around line 9-33: The modal lacks semantic accessibility and keyboard
handling: add role="dialog" and aria-modal="true" to the container, give the
<h4> a stable id (e.g., confirm-dialog-title) and add aria-labelledby
referencing it, programmatically move focus into the modal when it opens
(useEffect in the Modal component to focus the first button or the dialog
container) and restore focus on close, and handle keyboard events so pressing
Escape dispatches closeModal() (and ensure dispatch(clearCart()) remains on the
confirm button); consider adding basic focus trapping (or a focus-trap library)
so tabbing stays inside the modal while open.

In `@Week09/mint0326/mission2/src/features/cart/cartSlice.ts`:
- Around line 33-37: The decrease reducer in cartSlice currently decrements
cartItem.amount without a lower bound, allowing negative quantities; update the
decrease handler (the decrease reducer accessing state.cartItems and
action.payload) to check if the found cartItem.amount is <= 1 and, in that case,
remove the item from state.cartItems (e.g. filter out by id) or clamp to 1
instead of decrementing; otherwise perform cartItem.amount -= 1. Ensure you
reference the same action payload id and mutate state through the slice reducers
consistently.

In `@Week09/mint0326/mission2/src/index.css`:
- Around line 5-6: 현재 font-family 선언에서 공백이 없는 폰트명에 따옴표가 있어 stylelint의
font-family-name-quotes 규칙을 위반합니다; index.css의 font-family 줄에서 'Roboto',
'Oxygen', 'Ubuntu', 'Cantarell' 따옴표를 제거하고 공백이 있는 'Fira Sans', 'Droid Sans',
'Helvetica Neue' 등은 그대로 유지하도록 수정하세요.

---

Nitpick comments:
In `@Week09/mint0326/mission1/src/constants/cartItems.ts`:
- Around line 1-7: The CartItemType interface currently types price as string
which risks parsing bugs and NaN; change the price property to number in the
CartItemType declaration and update any related interfaces/usages (e.g., other
definitions referenced by "15-103") so numeric operations work without parsing,
and audit places that construct CartItemType or read price to remove string
parsing/formatting (convert incoming string values to numbers at the boundary or
during mapping) and ensure formatting for display happens at render time rather
than in the data model.

In `@Week09/mint0326/mission2/src/components/CartContainer.tsx`:
- Around line 1-8: Replace direct react-redux hooks in CartContainer with the
project-wide typed hooks: stop using useDispatch/useSelector and the RootState
generic here; import and use useAppDispatch and useAppSelector instead.
Concretely, update the imports to pull useAppDispatch and useAppSelector, call
const dispatch = useAppDispatch() and const { cartItems, total, amount } =
useAppSelector(state => state.cart), and leave usages like
dispatch(openModal(...)) and CartListItem unchanged. Ensure the typed hooks are
already defined in your store hooks file and reference those same hook names
across other components as well.

In `@Week09/mint0326/mission2/src/constants/cartItems.ts`:
- Around line 1-7: The CartItemType interface models price as a string which
forces repeated parsing and allows invalid non-numeric values; change the price
field to a number (CartItemType.price: number) and update any literal cart item
objects (where amounts like "price" strings are used) to numeric values so
downstream sum/total calculations use numbers directly; verify places that
construct or parse CartItemType (e.g., any cart item arrays or creators
referenced alongside CartItemType) are updated to pass numbers not strings.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fc1b1c90-3d1d-41e3-b9a9-ad7015b58b98

📥 Commits

Reviewing files that changed from the base of the PR and between dcd2f1f and 17179e3.

⛔ Files ignored due to path filters (12)
  • Week09/mint0326/mission1/package-lock.json is excluded by !**/package-lock.json
  • Week09/mint0326/mission1/public/favicon.svg is excluded by !**/*.svg
  • Week09/mint0326/mission1/public/icons.svg is excluded by !**/*.svg
  • Week09/mint0326/mission1/src/assets/hero.png is excluded by !**/*.png
  • Week09/mint0326/mission1/src/assets/react.svg is excluded by !**/*.svg
  • Week09/mint0326/mission1/src/assets/vite.svg is excluded by !**/*.svg
  • Week09/mint0326/mission2/package-lock.json is excluded by !**/package-lock.json
  • Week09/mint0326/mission2/public/favicon.svg is excluded by !**/*.svg
  • Week09/mint0326/mission2/public/icons.svg is excluded by !**/*.svg
  • Week09/mint0326/mission2/src/assets/hero.png is excluded by !**/*.png
  • Week09/mint0326/mission2/src/assets/react.svg is excluded by !**/*.svg
  • Week09/mint0326/mission2/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (40)
  • Week09/mint0326/mission1/.gitignore
  • Week09/mint0326/mission1/README.md
  • Week09/mint0326/mission1/eslint.config.js
  • Week09/mint0326/mission1/index.html
  • Week09/mint0326/mission1/package.json
  • Week09/mint0326/mission1/src/App.css
  • Week09/mint0326/mission1/src/App.tsx
  • Week09/mint0326/mission1/src/components/CartContainer.tsx
  • Week09/mint0326/mission1/src/components/CartListItem.tsx
  • Week09/mint0326/mission1/src/components/Navbar.tsx
  • Week09/mint0326/mission1/src/constants/cartItems.ts
  • Week09/mint0326/mission1/src/index.css
  • Week09/mint0326/mission1/src/main.tsx
  • Week09/mint0326/mission1/src/store/reducer/cartSlice.ts
  • Week09/mint0326/mission1/src/store/store.ts
  • Week09/mint0326/mission1/tsconfig.app.json
  • Week09/mint0326/mission1/tsconfig.json
  • Week09/mint0326/mission1/tsconfig.node.json
  • Week09/mint0326/mission1/vite.config.ts
  • Week09/mint0326/mission2/.gitignore
  • Week09/mint0326/mission2/README.md
  • Week09/mint0326/mission2/eslint.config.js
  • Week09/mint0326/mission2/index.html
  • Week09/mint0326/mission2/package.json
  • Week09/mint0326/mission2/src/App.css
  • Week09/mint0326/mission2/src/App.tsx
  • Week09/mint0326/mission2/src/components/CartContainer.tsx
  • Week09/mint0326/mission2/src/components/CartListItem.tsx
  • Week09/mint0326/mission2/src/components/Modal.tsx
  • Week09/mint0326/mission2/src/components/Navbar.tsx
  • Week09/mint0326/mission2/src/constants/cartItems.ts
  • Week09/mint0326/mission2/src/features/cart/cartSlice.ts
  • Week09/mint0326/mission2/src/features/modal/modalSlice.ts
  • Week09/mint0326/mission2/src/index.css
  • Week09/mint0326/mission2/src/main.tsx
  • Week09/mint0326/mission2/src/store/store.ts
  • Week09/mint0326/mission2/tsconfig.app.json
  • Week09/mint0326/mission2/tsconfig.json
  • Week09/mint0326/mission2/tsconfig.node.json
  • Week09/mint0326/mission2/vite.config.ts

import { clearCart } from '../store/reducer/cartSlice';

const CartContainer = () => {
const dispatch = useDispatch();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 목적:
# 1) AppDispatch 타입이 store에 정의되어 있는지 확인
# 2) 공용 typed hook(useAppDispatch/useAppSelector) 존재 여부 확인
# 3) mission1 컴포넌트에서 미타이핑 useDispatch 사용 위치 확인

set -e

echo "== store 타입 확인 =="
rg -n "export type AppDispatch|configureStore" Week09/mint0326/mission1/src/store/store.ts -C 2 || true

echo
echo "== typed hooks 파일 존재 여부 확인 =="
fd -i "hook" Week09/mint0326/mission1/src || true
rg -n "useAppDispatch|useAppSelector" Week09/mint0326/mission1/src -C 2 || true

echo
echo "== 미타이핑 useDispatch 사용 위치 =="
rg -n "useDispatch\\(\\)" Week09/mint0326/mission1/src/components -C 2 || true

Repository: HSU-Makeus-Challenge-10th/Web

Length of output: 1338


Redux Toolkit 디스패치 타입을 AppDispatch로 고정
Week09/mint0326/mission1/src/components/CartContainer.tsx 7행의 useDispatch()는 그대로 두지 말고, Week09/mint0326/mission1/src/store/store.ts에서 export된 AppDispatch를 사용해 const dispatch = useDispatch<AppDispatch>();로 고정하거나(또는 useAppDispatch/useAppSelector 훅을 새로 추가해) 앱 전용 typed hook 방식으로 일관화하세요. 현재 useAppDispatch/useAppSelector는 코드베이스에서 확인되지 않았습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/components/CartContainer.tsx` at line 7, The
CartContainer.tsx uses useDispatch() without typing; import the AppDispatch type
exported from store.ts and change the declaration to useDispatch<AppDispatch>()
(or add and use a typed helper hook named useAppDispatch/useAppSelector across
the app) so dispatch is strongly typed; update the import in CartContainer.tsx
to reference AppDispatch from store.ts and replace the const dispatch =
useDispatch() with the typed variant (or call useAppDispatch()) to ensure
consistent typed Redux Toolkit dispatch usage.

Comment on lines +19 to +37
<button
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => {
if (amount === 1) {
dispatch(removeItem(id));
return;
}
dispatch(decrease(id));
}}
>
<span className="text-lg font-bold">-</span>
</button>
<p className="w-6 text-center font-bold text-lg">{amount}</p>
<button
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => dispatch(increase(id))}
>
<span className="text-lg font-bold">+</span>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

수량 조절 버튼에 아이템 문맥 기반 aria-label을 넣어 주세요.

Line 19-37의 +/-만으로는 스크린리더에서 어떤 상품의 수량 조절인지 불명확할 수 있습니다. 버튼별 aria-label을 추가하면 접근성이 크게 좋아집니다.

접근성 개선 예시
         <button
+          type="button"
+          aria-label={`${title} 수량 감소`}
           className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
           onClick={() => {
             if (amount === 1) {
               dispatch(removeItem(id));
               return;
             }
             dispatch(decrease(id));
           }}
         >
           <span className="text-lg font-bold">-</span>
         </button>
@@
         <button
+          type="button"
+          aria-label={`${title} 수량 증가`}
           className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
           onClick={() => dispatch(increase(id))}
         >
           <span className="text-lg font-bold">+</span>
         </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => {
if (amount === 1) {
dispatch(removeItem(id));
return;
}
dispatch(decrease(id));
}}
>
<span className="text-lg font-bold">-</span>
</button>
<p className="w-6 text-center font-bold text-lg">{amount}</p>
<button
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => dispatch(increase(id))}
>
<span className="text-lg font-bold">+</span>
</button>
<button
type="button"
aria-label={`${title} 수량 감소`}
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => {
if (amount === 1) {
dispatch(removeItem(id));
return;
}
dispatch(decrease(id));
}}
>
<span className="text-lg font-bold">-</span>
</button>
<p className="w-6 text-center font-bold text-lg">{amount}</p>
<button
type="button"
aria-label={`${title} 수량 증가`}
className="bg-gray-200 w-8 h-8 flex items-center justify-center rounded text-gray-600 hover:bg-gray-300 transition-colors"
onClick={() => dispatch(increase(id))}
>
<span className="text-lg font-bold">+</span>
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/components/CartListItem.tsx` around lines 19 -
37, The decrease/increase buttons in CartListItem render only "+" and "-" and
need descriptive aria-labels; update the two button elements (the one whose
onClick calls removeItem/decrease and the one that calls increase) to include
aria-label attributes that identify the product and action (e.g., "Decrease
quantity for {nameOrId}" and "Increase quantity for {nameOrId}"), using the
component's id prop or a name prop if available so screen readers know which
item's quantity is being adjusted.

Comment on lines +11 to +15
<div className="relative flex items-center">
<ShoppingCart className="w-8 h-8" />
<div className="absolute -top-2 -right-2 bg-[#ff007f] rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">
{amount}
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

장바구니 수량 배지에 접근성 라벨을 추가해 주세요.

Line 11-15는 시각적으로는 명확하지만, 보조기기에서는 “현재 장바구니 수량” 의미가 약합니다. 컨테이너에 aria-label을 넣고 아이콘은 aria-hidden="true" 처리하는 쪽이 안전합니다.

접근성 보강 예시
-      <div className="relative flex items-center">
-        <ShoppingCart className="w-8 h-8" />
+      <div
+        className="relative flex items-center"
+        aria-label={`장바구니 상품 수량 ${amount}개`}
+      >
+        <ShoppingCart className="w-8 h-8" aria-hidden="true" />
         <div className="absolute -top-2 -right-2 bg-[`#ff007f`] rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">
           {amount}
         </div>
       </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="relative flex items-center">
<ShoppingCart className="w-8 h-8" />
<div className="absolute -top-2 -right-2 bg-[#ff007f] rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">
{amount}
</div>
<div
className="relative flex items-center"
aria-label={`장바구니 상품 수량 ${amount}개`}
>
<ShoppingCart className="w-8 h-8" aria-hidden="true" />
<div className="absolute -top-2 -right-2 bg-[`#ff007f`] rounded-full w-6 h-6 flex items-center justify-center text-sm font-bold">
{amount}
</div>
</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/components/Navbar.tsx` around lines 11 - 15, The
cart quantity badge lacks accessible labeling for screen readers; update the
container wrapping the ShoppingCart icon and badge (the div with className
"relative flex items-center") to include a descriptive aria-label like "Cart,
{amount} items" or similar, and mark the ShoppingCart icon component with
aria-hidden="true" so assistive tech reads only the badge/label; ensure the
dynamic amount value (amount) remains visible to sighted users and is reflected
in the aria-label text.

Comment on lines +5 to +6
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Stylelint 오류를 유발하는 폰트 따옴표를 정리하세요.

단일 단어 폰트명(Roboto, Oxygen, Ubuntu, Cantarell)에 따옴표가 있어 lint 에러가 발생합니다.

수정 예시
 body {
   margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
+    Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
     sans-serif;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 5-5: Expected no quotes around "Roboto" (font-family-name-quotes)

(font-family-name-quotes)


[error] 5-5: Expected no quotes around "Oxygen" (font-family-name-quotes)

(font-family-name-quotes)


[error] 6-6: Expected no quotes around "Ubuntu" (font-family-name-quotes)

(font-family-name-quotes)


[error] 6-6: Expected no quotes around "Cantarell" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/index.css` around lines 5 - 6, font-family 선언에서
단일 단어 폰트명에 불필요한 따옴표가 있어 Stylelint 오류가 발생하므로 index.css의 font-family 속성에서
'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell' 같은 단어 폰트명에서 따옴표를 제거하고 복합명(예: 'Segoe
UI', 'Helvetica Neue')만 따옴표로 유지하도록 수정하세요.

Comment on lines +33 to +37
decrease: (state, action: PayloadAction<string>) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload);
if (cartItem) {
cartItem.amount -= 1;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

수량 감소 시 0 미만으로 내려가는 경로를 막아야 합니다.

현재 로직은 음수 수량까지 허용할 수 있어 합계가 비정상 값이 됩니다. 최소 수량을 보장하거나 1에서 감소 시 항목을 제거하세요.

수정 예시
     decrease: (state, action: PayloadAction<string>) => {
       const cartItem = state.cartItems.find((item) => item.id === action.payload);
       if (cartItem) {
-        cartItem.amount -= 1;
+        if (cartItem.amount <= 1) {
+          state.cartItems = state.cartItems.filter((item) => item.id !== action.payload);
+        } else {
+          cartItem.amount -= 1;
+        }
       }
     },
📝 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
decrease: (state, action: PayloadAction<string>) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload);
if (cartItem) {
cartItem.amount -= 1;
}
decrease: (state, action: PayloadAction<string>) => {
const cartItem = state.cartItems.find((item) => item.id === action.payload);
if (cartItem) {
if (cartItem.amount <= 1) {
state.cartItems = state.cartItems.filter((item) => item.id !== action.payload);
} else {
cartItem.amount -= 1;
}
}
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission1/src/store/reducer/cartSlice.ts` around lines 33 -
37, The decrease reducer currently allows cartItem.amount to go below zero;
update the decrease handler in cartSlice (decrease) to prevent negative amounts
by either clamping cartItem.amount = Math.max(0, cartItem.amount - 1) or by
removing the item from state.cartItems when cartItem.amount would drop below 1
(i.e., if cartItem.amount <= 1 then remove that item else decrement); reference
the decrease reducer and state.cartItems/cartItem.amount when applying the
change.

Comment on lines +9 to +33
<aside className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded p-8 w-80 text-center shadow-lg">
<h4 className="text-lg font-bold mb-6 text-gray-800">정말 삭제하시겠습니까?</h4>
<div className="flex justify-center gap-4">
<button
type="button"
className="px-6 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors font-semibold"
onClick={() => dispatch(closeModal())}
>
아니요
</button>
<button
type="button"
className="px-6 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition-colors font-semibold"
onClick={() => {
dispatch(clearCart());
dispatch(closeModal());
}}
>
</button>
</div>
</div>
</aside>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

모달 접근성 시맨틱과 키보드 상호작용이 빠져 있습니다.

Line 9~33의 모달은 role="dialog", aria-modal, 라벨 연결, Escape 닫기 같은 필수 상호작용이 없어 키보드/스크린리더 사용자 흐름이 깨질 수 있습니다.

수정 예시
+import { useEffect } from 'react';
 import { useDispatch } from 'react-redux';
 import { closeModal } from '../features/modal/modalSlice';
 import { clearCart } from '../features/cart/cartSlice';

 const Modal = () => {
   const dispatch = useDispatch();
+
+  useEffect(() => {
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') dispatch(closeModal());
+    };
+    window.addEventListener('keydown', onKeyDown);
+    return () => window.removeEventListener('keydown', onKeyDown);
+  }, [dispatch]);

   return (
-    <aside className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
-      <div className="bg-white rounded p-8 w-80 text-center shadow-lg">
-        <h4 className="text-lg font-bold mb-6 text-gray-800">정말 삭제하시겠습니까?</h4>
+    <aside className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
+      <div
+        role="dialog"
+        aria-modal="true"
+        aria-labelledby="clear-cart-title"
+        className="bg-white rounded p-8 w-80 text-center shadow-lg"
+      >
+        <h4 id="clear-cart-title" className="text-lg font-bold mb-6 text-gray-800">
+          정말 삭제하시겠습니까?
+        </h4>
         <div className="flex justify-center gap-4">
           <button
             type="button"
             className="px-6 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors font-semibold"
             onClick={() => dispatch(closeModal())}
+            autoFocus
           >
             아니요
           </button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission2/src/components/Modal.tsx` around lines 9 - 33, The
modal lacks semantic accessibility and keyboard handling: add role="dialog" and
aria-modal="true" to the container, give the <h4> a stable id (e.g.,
confirm-dialog-title) and add aria-labelledby referencing it, programmatically
move focus into the modal when it opens (useEffect in the Modal component to
focus the first button or the dialog container) and restore focus on close, and
handle keyboard events so pressing Escape dispatches closeModal() (and ensure
dispatch(clearCart()) remains on the confirm button); consider adding basic
focus trapping (or a focus-trap library) so tabbing stays inside the modal while
open.

Comment thread Week09/mint0326/mission2/src/features/cart/cartSlice.ts
Comment thread Week09/mint0326/mission2/src/index.css Outdated
Copy link
Copy Markdown
Contributor

@wantkdd wantkdd left a comment

Choose a reason for hiding this comment

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

Redux Toolkit 기반 장바구니와 모달 분리 흐름은 전반적으로 잘 구현되어 있는데, decrease 액션 자체는 수량을 0 아래로 내릴 수 있는 구조이고 UI에서만 제거를 보정하고 있어서 액션을 다른 곳에서 재사용할 때 상태 규칙이 깨질 수 있습니다. 수량 감소 시 1 이하라면 reducer 내부에서 바로 제거하도록 처리하면, 컴포넌트는 클릭 이벤트만 담당하고 장바구니 규칙은 slice가 책임지는 구조가 되어 더 안정적일 것 같습니다. Mission1/2의 기본 기능은 잘 잡혀 있으니 다음 정리 때 reducer 책임을 더 명확히 다듬어보면 좋겠습니다, 수고하셨습니다!

Copy link
Copy Markdown

@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 (4)
Week09/mint0326/mission3/src/constants/cartItems.ts (1)

1-7: ⚡ Quick win

price는 문자열보다 숫자 타입으로 고정하는 편이 안전합니다

Line 5가 string이라 계산 지점마다 형변환이 필요하고, 잘못된 값 유입 시 NaN이 런타임까지 늦게 드러납니다. 데이터 모델을 숫자로 맞추면 합계 계산/표시 코드가 단순해집니다.

♻️ 제안 코드
 export interface CartItemType {
   id: string;
   title: string;
   singer: string;
-  price: string;
+  price: number;
   img: string;
   amount: number;
 }

 const cartItems: CartItemType[] = [
   {
@@
-    price: '25000',
+    price: 25000,
@@
-    price: '18000',
+    price: 18000,
@@
-    price: '28000',
+    price: 28000,
-<h4 className="font-semibold text-gray-700">₩ {Number(price).toLocaleString()}</h4>
+<h4 className="font-semibold text-gray-700">₩ {price.toLocaleString()}</h4>

Also applies to: 15-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission3/src/constants/cartItems.ts` around lines 1 - 7,
Change the CartItemType.price from string to number to enforce numeric
operations: update the interface CartItemType so price: number, then adjust all
creators/parsers (where cart items are constructed or parsed from
API/localStorage) to parse/validate numeric values and reject or default invalid
inputs, and update render/formatting code to convert price to a localized string
only at display time; apply the same change where the model is duplicated (the
other occurrence referenced as 15-103) so all consumers compile against the
numeric price type.
Week09/mint0326/mission3/src/App.tsx (1)

8-12: ⚡ Quick win

Zustand 상태 구독을 selector로 좁혀 불필요한 재렌더를 줄이세요 (App.tsx)

Week09/mint0326/mission3/src/App.tsx에서 useStore() 전체 상태를 구독하고 있어 cartItems/isOpen/calculateTotals만 selector로 가져오도록 개선하는 게 좋습니다. 또한 components/Navbar.tsx, components/Modal.tsx, components/CartListItem.tsx, components/CartContainer.tsx에도 selector-less useStore() 호출이 존재합니다.

♻️ 제안 코드
-  const { cartItems, isOpen, calculateTotals } = useStore();
+  const cartItems = useStore((state) => state.cartItems);
+  const isOpen = useStore((state) => state.isOpen);
+  const calculateTotals = useStore((state) => state.calculateTotals);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission3/src/App.tsx` around lines 8 - 12, The components are
subscribing to the entire Zustand store via useStore() causing unnecessary
re-renders; update App.tsx to call useStore with a selector that returns only
cartItems, isOpen and calculateTotals (e.g. useStore(state => ({ cartItems:
state.cartItems, isOpen: state.isOpen, calculateTotals: state.calculateTotals
}))) and keep using calculateTotals in the useEffect dependency array; similarly
refactor components Navbar, Modal, CartListItem, and CartContainer to replace
bare useStore() calls with selectors that pick only the specific state or
actions they need (and use shallow equality if returning multiple fields) so
each component only subscribes to the minimal slices.
Week09/mint0326/mission3/src/store/useStore.ts (1)

20-20: 💤 Low value

객체 프로퍼티 단축 구문 사용 권장

cartItems: cartItems는 ES6 단축 구문을 사용하여 cartItems로 간소화할 수 있습니다.

♻️ 제안하는 수정
 const useStore = create<StoreState>((set) => ({
-  cartItems: cartItems,
+  cartItems,
   amount: 0,
   total: 0,
   isOpen: false,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission3/src/store/useStore.ts` at line 20, In the object
literal returned/defined in useStore (the property currently written as
"cartItems: cartItems"), replace the explicit key:value form with the ES6
property shorthand by using just "cartItems" so the object uses the concise
property syntax; locate the cartItems property in useStore.ts and update it
accordingly.
Week09/mint0326/mission3/src/components/Modal.tsx (1)

3-32: 🏗️ Heavy lift

접근성 향상을 위한 모달 개선 권장

모달 컴포넌트에 다음과 같은 접근성 기능을 추가하면 키보드 사용자와 스크린 리더 사용자의 경험을 개선할 수 있습니다:

  1. ARIA 속성 추가 (role="dialog", aria-modal="true", aria-labelledby)
  2. Escape 키로 모달 닫기
  3. 오버레이 클릭으로 모달 닫기
  4. 모달 열릴 때 첫 번째 버튼으로 포커스 이동
♻️ 접근성이 개선된 구현 예시
+import { useEffect, useRef } from 'react';
 import useStore from '../store/useStore';

 const Modal = () => {
   const { closeModal, clearCart } = useStore();
+  const confirmButtonRef = useRef<HTMLButtonElement>(null);
+
+  useEffect(() => {
+    // 모달이 열릴 때 첫 번째 버튼으로 포커스 이동
+    confirmButtonRef.current?.focus();
+
+    // Escape 키로 모달 닫기
+    const handleEscape = (e: KeyboardEvent) => {
+      if (e.key === 'Escape') closeModal();
+    };
+    document.addEventListener('keydown', handleEscape);
+    return () => document.removeEventListener('keydown', handleEscape);
+  }, [closeModal]);

   return (
-    <aside className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
-      <div className="bg-white rounded p-8 w-80 text-center shadow-lg">
-        <h4 className="text-lg font-bold mb-6 text-gray-800">정말 삭제하시겠습니까?</h4>
+    <aside 
+      className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      onClick={closeModal}
+      role="dialog"
+      aria-modal="true"
+      aria-labelledby="modal-title"
+    >
+      <div 
+        className="bg-white rounded p-8 w-80 text-center shadow-lg"
+        onClick={(e) => e.stopPropagation()}
+      >
+        <h4 id="modal-title" className="text-lg font-bold mb-6 text-gray-800">
+          정말 삭제하시겠습니까?
+        </h4>
         <div className="flex justify-center gap-4">
           <button
+            ref={confirmButtonRef}
             type="button"
             className="px-6 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition-colors font-semibold"
             onClick={() => closeModal()}
           >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Week09/mint0326/mission3/src/components/Modal.tsx` around lines 3 - 32,
Update the Modal component to improve accessibility: add role="dialog",
aria-modal="true" and aria-labelledby referencing the heading element (give the
<h4> an id) on the modal container; make the overlay (<aside>) clickable to call
closeModal when clicking outside the dialog (but not when clicking inside the
dialog box), add a keydown handler on mount to close the modal on Escape (use
closeModal) and clean it up on unmount, and move focus to the first action
button when the modal mounts (return focus appropriately on unmount). Use the
existing Modal function and the useStore exports closeModal and clearCart to
implement these behaviors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Week09/mint0326/mission3/src/App.tsx`:
- Around line 8-12: The components are subscribing to the entire Zustand store
via useStore() causing unnecessary re-renders; update App.tsx to call useStore
with a selector that returns only cartItems, isOpen and calculateTotals (e.g.
useStore(state => ({ cartItems: state.cartItems, isOpen: state.isOpen,
calculateTotals: state.calculateTotals }))) and keep using calculateTotals in
the useEffect dependency array; similarly refactor components Navbar, Modal,
CartListItem, and CartContainer to replace bare useStore() calls with selectors
that pick only the specific state or actions they need (and use shallow equality
if returning multiple fields) so each component only subscribes to the minimal
slices.

In `@Week09/mint0326/mission3/src/components/Modal.tsx`:
- Around line 3-32: Update the Modal component to improve accessibility: add
role="dialog", aria-modal="true" and aria-labelledby referencing the heading
element (give the <h4> an id) on the modal container; make the overlay (<aside>)
clickable to call closeModal when clicking outside the dialog (but not when
clicking inside the dialog box), add a keydown handler on mount to close the
modal on Escape (use closeModal) and clean it up on unmount, and move focus to
the first action button when the modal mounts (return focus appropriately on
unmount). Use the existing Modal function and the useStore exports closeModal
and clearCart to implement these behaviors.

In `@Week09/mint0326/mission3/src/constants/cartItems.ts`:
- Around line 1-7: Change the CartItemType.price from string to number to
enforce numeric operations: update the interface CartItemType so price: number,
then adjust all creators/parsers (where cart items are constructed or parsed
from API/localStorage) to parse/validate numeric values and reject or default
invalid inputs, and update render/formatting code to convert price to a
localized string only at display time; apply the same change where the model is
duplicated (the other occurrence referenced as 15-103) so all consumers compile
against the numeric price type.

In `@Week09/mint0326/mission3/src/store/useStore.ts`:
- Line 20: In the object literal returned/defined in useStore (the property
currently written as "cartItems: cartItems"), replace the explicit key:value
form with the ES6 property shorthand by using just "cartItems" so the object
uses the concise property syntax; locate the cartItems property in useStore.ts
and update it accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d0a1b62c-c98c-438f-8d8f-a697ae152fc7

📥 Commits

Reviewing files that changed from the base of the PR and between 17179e3 and d38fe2e.

⛔ Files ignored due to path filters (6)
  • Week09/mint0326/mission3/package-lock.json is excluded by !**/package-lock.json
  • Week09/mint0326/mission3/public/favicon.svg is excluded by !**/*.svg
  • Week09/mint0326/mission3/public/icons.svg is excluded by !**/*.svg
  • Week09/mint0326/mission3/src/assets/hero.png is excluded by !**/*.png
  • Week09/mint0326/mission3/src/assets/react.svg is excluded by !**/*.svg
  • Week09/mint0326/mission3/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • Week09/mint0326/mission2/src/App.tsx
  • Week09/mint0326/mission2/src/components/CartContainer.tsx
  • Week09/mint0326/mission2/src/components/CartListItem.tsx
  • Week09/mint0326/mission2/src/components/Modal.tsx
  • Week09/mint0326/mission2/src/components/Navbar.tsx
  • Week09/mint0326/mission2/src/features/cart/cartSlice.ts
  • Week09/mint0326/mission2/src/index.css
  • Week09/mint0326/mission2/src/store/hooks.ts
  • Week09/mint0326/mission3/.gitignore
  • Week09/mint0326/mission3/README.md
  • Week09/mint0326/mission3/eslint.config.js
  • Week09/mint0326/mission3/index.html
  • Week09/mint0326/mission3/package.json
  • Week09/mint0326/mission3/src/App.css
  • Week09/mint0326/mission3/src/App.tsx
  • Week09/mint0326/mission3/src/components/CartContainer.tsx
  • Week09/mint0326/mission3/src/components/CartListItem.tsx
  • Week09/mint0326/mission3/src/components/Modal.tsx
  • Week09/mint0326/mission3/src/components/Navbar.tsx
  • Week09/mint0326/mission3/src/constants/cartItems.ts
  • Week09/mint0326/mission3/src/index.css
  • Week09/mint0326/mission3/src/main.tsx
  • Week09/mint0326/mission3/src/store/useStore.ts
  • Week09/mint0326/mission3/tsconfig.app.json
  • Week09/mint0326/mission3/tsconfig.json
  • Week09/mint0326/mission3/tsconfig.node.json
  • Week09/mint0326/mission3/vite.config.ts
✅ Files skipped from review due to trivial changes (10)
  • Week09/mint0326/mission3/vite.config.ts
  • Week09/mint0326/mission3/src/components/Navbar.tsx
  • Week09/mint0326/mission3/tsconfig.app.json
  • Week09/mint0326/mission3/eslint.config.js
  • Week09/mint0326/mission3/index.html
  • Week09/mint0326/mission2/src/store/hooks.ts
  • Week09/mint0326/mission3/tsconfig.json
  • Week09/mint0326/mission3/package.json
  • Week09/mint0326/mission3/README.md
  • Week09/mint0326/mission3/.gitignore
🚧 Files skipped from review as they are similar to previous changes (6)
  • Week09/mint0326/mission2/src/App.tsx
  • Week09/mint0326/mission2/src/components/CartContainer.tsx
  • Week09/mint0326/mission2/src/components/Modal.tsx
  • Week09/mint0326/mission2/src/index.css
  • Week09/mint0326/mission2/src/components/CartListItem.tsx
  • Week09/mint0326/mission2/src/features/cart/cartSlice.ts

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants