Create Week9 Mission1~3#80
Conversation
📝 Walkthrough개요
Walkthroughmission1/mission2/mission3에 각기 독립된 Vite+React 앱 템플릿과 스타일, 상태관리(cart 슬라이스 또는 Zustand), 컴포넌트(Navbar/CartContainer/CartListItem/Modal), 빌드·린트·타입 설정이 추가되었습니다. ChangesMission1: Redux 장바구니 애플리케이션
Mission2: Redux 장바구니 애플리케이션 (모달 기능 추가)
Mission3: Zustand 기반 장바구니 샘플
추정 코드 리뷰 노력🎯 3 (Moderate) | ⏱️ ~25 minutes 관련 가능성이 있는 PR
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add 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: 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-redux의useDispatch/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
⛔ Files ignored due to path filters (12)
Week09/mint0326/mission1/package-lock.jsonis excluded by!**/package-lock.jsonWeek09/mint0326/mission1/public/favicon.svgis excluded by!**/*.svgWeek09/mint0326/mission1/public/icons.svgis excluded by!**/*.svgWeek09/mint0326/mission1/src/assets/hero.pngis excluded by!**/*.pngWeek09/mint0326/mission1/src/assets/react.svgis excluded by!**/*.svgWeek09/mint0326/mission1/src/assets/vite.svgis excluded by!**/*.svgWeek09/mint0326/mission2/package-lock.jsonis excluded by!**/package-lock.jsonWeek09/mint0326/mission2/public/favicon.svgis excluded by!**/*.svgWeek09/mint0326/mission2/public/icons.svgis excluded by!**/*.svgWeek09/mint0326/mission2/src/assets/hero.pngis excluded by!**/*.pngWeek09/mint0326/mission2/src/assets/react.svgis excluded by!**/*.svgWeek09/mint0326/mission2/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (40)
Week09/mint0326/mission1/.gitignoreWeek09/mint0326/mission1/README.mdWeek09/mint0326/mission1/eslint.config.jsWeek09/mint0326/mission1/index.htmlWeek09/mint0326/mission1/package.jsonWeek09/mint0326/mission1/src/App.cssWeek09/mint0326/mission1/src/App.tsxWeek09/mint0326/mission1/src/components/CartContainer.tsxWeek09/mint0326/mission1/src/components/CartListItem.tsxWeek09/mint0326/mission1/src/components/Navbar.tsxWeek09/mint0326/mission1/src/constants/cartItems.tsWeek09/mint0326/mission1/src/index.cssWeek09/mint0326/mission1/src/main.tsxWeek09/mint0326/mission1/src/store/reducer/cartSlice.tsWeek09/mint0326/mission1/src/store/store.tsWeek09/mint0326/mission1/tsconfig.app.jsonWeek09/mint0326/mission1/tsconfig.jsonWeek09/mint0326/mission1/tsconfig.node.jsonWeek09/mint0326/mission1/vite.config.tsWeek09/mint0326/mission2/.gitignoreWeek09/mint0326/mission2/README.mdWeek09/mint0326/mission2/eslint.config.jsWeek09/mint0326/mission2/index.htmlWeek09/mint0326/mission2/package.jsonWeek09/mint0326/mission2/src/App.cssWeek09/mint0326/mission2/src/App.tsxWeek09/mint0326/mission2/src/components/CartContainer.tsxWeek09/mint0326/mission2/src/components/CartListItem.tsxWeek09/mint0326/mission2/src/components/Modal.tsxWeek09/mint0326/mission2/src/components/Navbar.tsxWeek09/mint0326/mission2/src/constants/cartItems.tsWeek09/mint0326/mission2/src/features/cart/cartSlice.tsWeek09/mint0326/mission2/src/features/modal/modalSlice.tsWeek09/mint0326/mission2/src/index.cssWeek09/mint0326/mission2/src/main.tsxWeek09/mint0326/mission2/src/store/store.tsWeek09/mint0326/mission2/tsconfig.app.jsonWeek09/mint0326/mission2/tsconfig.jsonWeek09/mint0326/mission2/tsconfig.node.jsonWeek09/mint0326/mission2/vite.config.ts
| import { clearCart } from '../store/reducer/cartSlice'; | ||
|
|
||
| const CartContainer = () => { | ||
| const dispatch = useDispatch(); |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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.
| <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> |
There was a problem hiding this comment.
수량 조절 버튼에 아이템 문맥 기반 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.
| <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.
| <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> |
There was a problem hiding this comment.
장바구니 수량 배지에 접근성 라벨을 추가해 주세요.
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.
| <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.
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', | ||
| 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', |
There was a problem hiding this comment.
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.
| 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')만 따옴표로 유지하도록 수정하세요.
| decrease: (state, action: PayloadAction<string>) => { | ||
| const cartItem = state.cartItems.find((item) => item.id === action.payload); | ||
| if (cartItem) { | ||
| cartItem.amount -= 1; | ||
| } |
There was a problem hiding this comment.
수량 감소 시 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.
| 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.
| <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> | ||
| ); |
There was a problem hiding this comment.
모달 접근성 시맨틱과 키보드 상호작용이 빠져 있습니다.
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.
wantkdd
left a comment
There was a problem hiding this comment.
Redux Toolkit 기반 장바구니와 모달 분리 흐름은 전반적으로 잘 구현되어 있는데, decrease 액션 자체는 수량을 0 아래로 내릴 수 있는 구조이고 UI에서만 제거를 보정하고 있어서 액션을 다른 곳에서 재사용할 때 상태 규칙이 깨질 수 있습니다. 수량 감소 시 1 이하라면 reducer 내부에서 바로 제거하도록 처리하면, 컴포넌트는 클릭 이벤트만 담당하고 장바구니 규칙은 slice가 책임지는 구조가 되어 더 안정적일 것 같습니다. Mission1/2의 기본 기능은 잘 잡혀 있으니 다음 정리 때 reducer 책임을 더 명확히 다듬어보면 좋겠습니다, 수고하셨습니다!
There was a problem hiding this comment.
🧹 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 winZustand 상태 구독을 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-lessuseStore()호출이 존재합니다.♻️ 제안 코드
- 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접근성 향상을 위한 모달 개선 권장
모달 컴포넌트에 다음과 같은 접근성 기능을 추가하면 키보드 사용자와 스크린 리더 사용자의 경험을 개선할 수 있습니다:
- ARIA 속성 추가 (
role="dialog",aria-modal="true",aria-labelledby)- Escape 키로 모달 닫기
- 오버레이 클릭으로 모달 닫기
- 모달 열릴 때 첫 번째 버튼으로 포커스 이동
♻️ 접근성이 개선된 구현 예시
+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
⛔ Files ignored due to path filters (6)
Week09/mint0326/mission3/package-lock.jsonis excluded by!**/package-lock.jsonWeek09/mint0326/mission3/public/favicon.svgis excluded by!**/*.svgWeek09/mint0326/mission3/public/icons.svgis excluded by!**/*.svgWeek09/mint0326/mission3/src/assets/hero.pngis excluded by!**/*.pngWeek09/mint0326/mission3/src/assets/react.svgis excluded by!**/*.svgWeek09/mint0326/mission3/src/assets/vite.svgis excluded by!**/*.svg
📒 Files selected for processing (27)
Week09/mint0326/mission2/src/App.tsxWeek09/mint0326/mission2/src/components/CartContainer.tsxWeek09/mint0326/mission2/src/components/CartListItem.tsxWeek09/mint0326/mission2/src/components/Modal.tsxWeek09/mint0326/mission2/src/components/Navbar.tsxWeek09/mint0326/mission2/src/features/cart/cartSlice.tsWeek09/mint0326/mission2/src/index.cssWeek09/mint0326/mission2/src/store/hooks.tsWeek09/mint0326/mission3/.gitignoreWeek09/mint0326/mission3/README.mdWeek09/mint0326/mission3/eslint.config.jsWeek09/mint0326/mission3/index.htmlWeek09/mint0326/mission3/package.jsonWeek09/mint0326/mission3/src/App.cssWeek09/mint0326/mission3/src/App.tsxWeek09/mint0326/mission3/src/components/CartContainer.tsxWeek09/mint0326/mission3/src/components/CartListItem.tsxWeek09/mint0326/mission3/src/components/Modal.tsxWeek09/mint0326/mission3/src/components/Navbar.tsxWeek09/mint0326/mission3/src/constants/cartItems.tsWeek09/mint0326/mission3/src/index.cssWeek09/mint0326/mission3/src/main.tsxWeek09/mint0326/mission3/src/store/useStore.tsWeek09/mint0326/mission3/tsconfig.app.jsonWeek09/mint0326/mission3/tsconfig.jsonWeek09/mint0326/mission3/tsconfig.node.jsonWeek09/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
📝 미션 번호
8주차 Misson 1~3
📋 구현 사항
Mission1
- tailwind 스타일링
- mock data 사용
- redux store 생성
- cartslice 설계
Mission2
Mission3
📎 스크린샷
Mission1
2026-05-27.003649_1.mp4
Mission2
2026-05-27.011355.mp4
Mission3
2026-05-28.015134.mp4
✅ 체크리스트
🤔 질문 사항
Summary by CodeRabbit
릴리스 노트
New Features
Documentation
Chores