-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 밸런스 게임 수정 및 삭제 로직 구현 #268
base: dev
Are you sure you want to change the base?
Conversation
Walkthrough이 풀 리퀘스트는 밸런스 게임의 수정 기능을 구현하기 위한 광범위한 변경 사항을 포함하고 있습니다. 주요 변경 사항은 라우팅 구조 업데이트, 게임 수정을 위한 새로운 페이지 및 컴포넌트 추가, 이미지 업로드 및 게임 데이터 관리를 위한 새로운 훅 도입, 그리고 관련 API 및 유틸리티 함수의 확장을 포함합니다. Changes
Assessment against linked issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
🧹 Nitpick comments (25)
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (1)
91-113
: 게스트 투표 로직 개선 제안현재 게스트 투표 로직이 다소 복잡합니다. 다음과 같이 단순화할 수 있습니다:
const handleGuestGameVote = ( selectedOption: MyVoteOption, voteOption: VoteOption, ) => { - const updatedVotes = [...guestVotedList]; - const currentVoteIndex = updatedVotes.findIndex( - (vote) => vote.gameId === game?.gameDetailResponses[currentStage]?.id, - ); + const gameId = game?.gameDetailResponses[currentStage]?.id; + if (!gameId) return; - if (!selectedOption) { - updatedVotes.push({ - gameId: game?.gameDetailResponses[currentStage]?.id as number, - votedOption: voteOption, - }); - } else if (selectedOption === voteOption) { - updatedVotes.splice(currentVoteIndex, 1); - } else { - updatedVotes[currentVoteIndex].votedOption = voteOption; - } + const updatedVotes = guestVotedList.filter(vote => vote.gameId !== gameId); + if (!selectedOption || selectedOption !== voteOption) { + updatedVotes.push({ gameId, votedOption: voteOption }); + } setGuestVotedList(updatedVotes); localStorage.setItem(`game_${gameSetId}`, JSON.stringify(updatedVotes)); };src/pages/BalanceGamePage/BalanceGamePage.tsx (4)
1-19
: 불필요한 반복 Import 방지 제안
현재 많은 훅과 컴포넌트를 한 파일에서 임포트 중입니다. 관리 편의를 위해 관련 기능(예: API 훅)들을 하나의 인덱스 파일에서 통합 임포트하여 import 구문을 간소화하면 유지보수성이 향상될 수 있습니다.
54-76
: 삭제 처리 로직 예외 케이스 검토
삭제 로직 실행에 성공 또는 실패 시 토스트 모달로 처리하고 있습니다. 네트워크 실패(오프라인 등)나 API 예상치 못한 응답 코드 같은 예외 상황에 대해서도 다양한 테스트를 진행하는 것을 권장드립니다.
78-87
: 신고 기능 구현 확인
현재 신고 기능이 모달 열기와 닫기 정도로 구현되어 있습니다. 추후 신고 API 연동 계획이 있으시다면, 에러 처리나 성공 시 유저에게 명확한 안내가 표시되도록 보강하시면 좋겠습니다.
89-129
: 조건 분기 구조 간소화 제안
currentStage가 10인지 체크하여 다른 컴포넌트를 렌더링하는 로직은 읽기 쉽지만, 추후 조건이 늘어나면 분기문이 복잡해질 수 있습니다. 별도 함수나 객체 맵핑을 통해 분기를 관리하면 가독성을 더 높일 수 있을 것 같습니다.src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx (1)
71-91
: 게임 정보 수정 API 에러 처리
mutate 호출 시 onError 처리에서 에러 응답 정보를 좀 더 구체적으로 사용자에게 알려주면 좋을 것 같습니다. 예: 400인지, 500인지 등.src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx (4)
17-26
: 인터페이스 설계 검토
onDraftLoad, handleTagEditClick 같은 선택적 프로퍼티가 늘어나고 있는데, 향후 기능 확장 시 공통 인터페이스 분리나 컴포넌트 분해로 관리 부담을 낮출 것을 제안드립니다.
42-44
: currentStage와 clearInput 동시 관리
두 상태를 각각 저장하고 있는데, setCurrentStage 시 clearInput을 초기화하는 식으로, 상호 의존 로직을 간단하게 관리할 수 있는지 한 번 고려해 보세요.
49-70
: updateOption 함수의 가독성 제안
중첩 맵으로 인해 가독성이 약간 저하될 수 있습니다. 한 단계 더 나누거나 헬퍼 함수를 추출하여 “옵션 업데이트” 로직을 분명히 하는 방법을 고려해 보세요.
72-90
: validateStage 함수의 리턴 구조
true 또는 에러 메시지로 분기하는 방식은 단순 명료합니다. 다만, 다양한 에러 종류가 생길 경우 상수를 한 파일에서 관리하거나, Error 객체를 던지는 구조로 확장하면 더 견고할 수 있겠습니다.src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx (2)
56-56
: 이미지 처리 훅 도입
useImageHandlers 훅을 도입하여 업로드 및 삭제 로직을 중앙집중화했습니다. 이미지 업로드 후 에러가 나면 재시도 방안 등이 있는지 한 번 살펴보세요.
81-89
: 이미지 삭제 로직 모달 처리
사용자가 삭제를 확정하기 전까지 confirm 과정을 거치도록 설계되었습니다. UX 차원에서 “삭제할 이미지를 미리보기” 등의 추가 기능을 검토해봐도 좋을 것 같습니다.src/hooks/modal/useModal.ts (1)
3-7
: 인터페이스 문서화 추가 제안인터페이스가 잘 정의되어 있지만, 각 속성에 대한 JSDoc 문서화를 추가하면 더 좋을 것 같습니다.
interface UseModalReturn { + /** 모달의 현재 표시 상태 */ isOpen: boolean; + /** 모달을 여는 함수 */ openModal: () => void; + /** 모달을 닫는 함수 */ closeModal: () => void; }src/hooks/api/game/useDeleteGameSetMutation.ts (1)
12-14
: 불필요한 Promise 체이닝 제거
then(() => {})
체이닝이 불필요합니다. 직접deleteBySetId
를 반환하는 것이 더 깔끔할 것 같습니다.- mutationFn: ({ gameSetId }) => deleteBySetId(gameSetId).then(() => {}), + mutationFn: ({ gameSetId }) => deleteBySetId(gameSetId),src/components/organisms/BalanceGameCreation/BalanceGameCreation.style.ts (1)
45-53
: z-index 값의 표준화가 필요합니다.현재 하드코딩된 z-index 값(50)을 상수로 분리하여 관리하는 것이 좋습니다. 또한 위치 값(top: 16px, right: 21px)도 변수로 관리하면 유지보수가 더 용이할 것 같습니다.
다음과 같은 방식으로 개선해보세요:
+const Z_INDICES = { + TAG_EDIT_BUTTON: 50 +} as const; + +const POSITION_VALUES = { + TAG_EDIT_BUTTON: { + top: '16px', + right: '21px' + } +} as const; export const tagEditButtonContainer = css({ position: 'absolute', - top: '16px', - right: '21px', + top: POSITION_VALUES.TAG_EDIT_BUTTON.top, + right: POSITION_VALUES.TAG_EDIT_BUTTON.right, display: 'flex', alignItems: 'center', justifyContent: 'center', - zIndex: '50', + zIndex: Z_INDICES.TAG_EDIT_BUTTON, });src/pages/BalanceGamePage/BalanceGamePage.style.ts (1)
36-42
: 중앙 정렬 스타일이 적절하게 구현되었습니다.모달 컴포넌트의 중앙 정렬을 위한 스타일이 잘 정의되어 있습니다. 다만, z-index 값의 관리가 필요해 보입니다.
z-index 값을 상수로 분리하여 관리하는 것을 추천드립니다.
src/types/game.ts (1)
7-7
: 타입 정의에 JSDoc 문서화 추가 필요
fileId
필드에 대한 문서화가 누락되어 있습니다. 해당 필드의 용도와 null 값이 허용되는 상황에 대한 설명이 필요합니다.다음과 같이 문서화를 추가할 것을 제안합니다:
export interface GameOption { id: number; name: string; imgUrl: string | null; + /** 이미지 파일의 ID. 이미지가 업로드되지 않은 경우 null 또는 undefined */ fileId?: number | null; description: string; optionType: 'A' | 'B'; }
export interface BalanceGameOption { id: number; name: string; imgUrl: string; description: string; optionType: 'A' | 'B'; imageFile?: File | null; + /** 이미지 파일의 ID. 이미지가 업로드되지 않은 경우 null 또는 undefined */ fileId?: number | null; }
Also applies to: 61-61
src/stories/organisms/BalanceGameCreation.stories.tsx (1)
28-43
: 테스트 데이터 다양성 개선 필요현재 더미 데이터가 기본적인 케이스만 다루고 있습니다. 다음과 같은 엣지 케이스들을 추가로 고려해주세요:
- 긴 텍스트가 포함된 케이스
- 특수 문자가 포함된 케이스
- 이미지 URL이 있는 케이스
- fileId가 있는 케이스
다음과 같이 더미 데이터를 확장할 것을 제안합니다:
const dummyGames: BalanceGameSet[] = [ { description: '첫 번째 스테이지 설명', gameOptions: [ - { id: 1, name: '옵션 A', imgUrl: '', description: '', optionType: 'A' }, - { id: 2, name: '옵션 B', imgUrl: '', description: '', optionType: 'B' }, + { + id: 1, + name: '매우 긴 옵션 이름입니다. 이렇게 긴 텍스트가 들어가도 UI가 깨지지 않아야 합니다.', + imgUrl: 'https://example.com/image.jpg', + fileId: 123, + description: '특수문자 테스트: !@#$%^&*()', + optionType: 'A' + }, + { + id: 2, + name: '옵션 B', + imgUrl: '', + description: '', + optionType: 'B' + }, ], }, // ... 나머지 더미 데이터 ];src/hooks/game/useImageHandlers.ts (2)
32-57
: 이미지 변경 함수의 에러 처리를 구체화해주세요.
onImageChange
함수의 에러 처리가 너무 일반적입니다. 사용자에게 더 명확한 피드백을 제공할 수 있도록 개선이 필요합니다.const onImageChange = async ( stageIndex: number, optionIndex: number, imageFile: File, updateGames: ( updater: (games: BalanceGameSet[]) => BalanceGameSet[], ) => void, -): Promise<boolean> => { +): Promise<{ success: boolean; error?: string }> => { try { const { imgUrl, fileId } = await handleImageUpload(imageFile, { type: 'GAME_OPTION', }); updateGames((prevGames) => { const updatedGames = [...prevGames]; updatedGames[stageIndex].gameOptions[optionIndex] = { ...updatedGames[stageIndex].gameOptions[optionIndex], imgUrl, fileId, }; return updatedGames; }); - return true; + return { success: true }; } catch (error) { - return false; + return { + success: false, + error: error instanceof Error ? error.message : '이미지 업로드 중 오류가 발생했습니다.', + }; } };
59-78
: 이미지 삭제 함수에 확인 로직 추가가 필요합니다.이미지 삭제 시 실수로 인한 삭제를 방지하기 위한 검증 로직이 필요합니다.
const deleteImage = ( stageIndex: number, optionIndex: number, updateGames: ( updater: (games: BalanceGameSet[]) => BalanceGameSet[], ) => void, ) => { + // 삭제 전 현재 이미지 존재 여부 확인 + const currentGame = games[stageIndex]; + const currentOption = currentGame?.gameOptions[optionIndex]; + + if (!currentOption?.imgUrl) { + return; + } + updateGames((prevGames) => { const updatedGames = [...prevGames]; updatedGames[stageIndex] = { ...updatedGames[stageIndex], gameOptions: updatedGames[stageIndex].gameOptions.map((option, idx) => idx === optionIndex ? { ...option, imgUrl: '', fileId: null } : option, ), }; return updatedGames; }); };src/components/molecules/TagModal/TagModal.tsx (2)
23-24
: 상태 초기화 로직 개선이 필요합니다.상태 초기화 시 null 체크와 타입 일관성이 부족합니다. 또한 빈 문자열 처리가 불명확합니다.
-const [mainTag, setMainTag] = useState<string | null>(initialMainTag ?? null); -const [subTag, setSubTag] = useState<string>(initialSubTag ?? ''); +const [mainTag, setMainTag] = useState<string>(() => { + return initialMainTag?.trim() || ''; +}); +const [subTag, setSubTag] = useState<string>(() => { + return initialSubTag?.trim() || ''; +});
26-31
: useEffect 의존성 배열 최적화가 필요합니다.useEffect의 의존성이 불필요하게 많이 포함되어 있습니다. 또한 상태 초기화 시점을 더 명확하게 제어할 필요가 있습니다.
useEffect(() => { - if (isOpen) { - setMainTag(initialMainTag ?? null); - setSubTag(initialSubTag ?? ''); - } -}, [isOpen, initialMainTag, initialSubTag]); + const resetTags = () => { + setMainTag(initialMainTag?.trim() || ''); + setSubTag(initialSubTag?.trim() || ''); + }; + + if (isOpen) { + resetTags(); + } +}, [isOpen]);src/constants/message.ts (2)
57-62
: 에러 메시지 구조 개선이 필요합니다.게임 수정 및 삭제 실패에 대한 구체적인 원인을 사용자에게 전달하면 좋을 것 같습니다.
다음과 같이 더 상세한 에러 메시지를 추가하는 것을 제안합니다:
EDITGAME: { FAIL: '게임 수정에 실패했습니다.', + INVALID_DATA: '올바르지 않은 게임 데이터입니다.', + PERMISSION_DENIED: '게임을 수정할 권한이 없습니다.', }, DELETEGAME: { FAIL: '게임 삭제에 실패했습니다.', + PERMISSION_DENIED: '게임을 삭제할 권한이 없습니다.', },
109-111
: 태그 수정 메시지의 위치를 재고려해보세요.태그 관련 메시지가 향후 늘어날 것을 고려하여, 다른 메시지들처럼 객체로 구조화하는 것이 좋을 것 같습니다.
다음과 같은 구조를 제안합니다:
- TAG: { - EDIT: '태그 수정이 완료되었습니다!', - }, + TAG: { + SUCCESS: { + EDIT: '태그 수정이 완료되었습니다!', + CREATE: '태그 생성이 완료되었습니다!', + DELETE: '태그 삭제가 완료되었습니다!', + }, + ERROR: { + EDIT: '태그 수정에 실패했습니다.', + INVALID: '올바르지 않은 태그입니다.', + }, + },src/App.tsx (1)
10-10
: import 문 정렬이 필요합니다.새로 추가된 import 문이 알파벳 순서로 정렬되어 있지 않습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
src/App.tsx
(2 hunks)src/api/game.ts
(1 hunks)src/components/molecules/TagModal/TagModal.tsx
(2 hunks)src/components/organisms/BalanceGameCreation/BalanceGameCreation.style.ts
(1 hunks)src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx
(3 hunks)src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
(1 hunks)src/constants/message.ts
(2 hunks)src/constants/path.ts
(1 hunks)src/hooks/api/game/useDeleteGameSetMutation.ts
(1 hunks)src/hooks/api/game/useUpdateGameMutation.ts
(1 hunks)src/hooks/game/useBalanceGameCreation.ts
(0 hunks)src/hooks/game/useImageHandlers.ts
(1 hunks)src/hooks/modal/useModal.ts
(1 hunks)src/pages/BalanceGameCreationPage/BalanceGameCreationPage.style.ts
(1 hunks)src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx
(7 hunks)src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx
(1 hunks)src/pages/BalanceGamePage/BalanceGamePage.style.ts
(1 hunks)src/pages/BalanceGamePage/BalanceGamePage.tsx
(1 hunks)src/stories/organisms/BalanceGameCreation.stories.tsx
(2 hunks)src/types/game.ts
(2 hunks)src/utils/balanceGameUtils.ts
(2 hunks)
💤 Files with no reviewable changes (1)
- src/hooks/game/useBalanceGameCreation.ts
🧰 Additional context used
🪛 eslint
src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx
[error] 153-153: Expected 'undefined' and instead saw 'void'.
(no-void)
[error] 171-171: Expected 'undefined' and instead saw 'void'.
(no-void)
🔇 Additional comments (17)
src/pages/BalanceGameCreationPage/BalanceGameCreationPage.style.ts (2)
5-5
: 네이밍 컨벤션 변경이 적절합니다.
CSS 객체의 경우 camelCase를 사용하는 것이 일반적인 JavaScript/TypeScript 컨벤션에 더 잘 부합합니다. PageContainer
에서 pageContainer
로의 변경은 적절한 판단입니다.
Line range hint 5-11
: 변수명 변경으로 인한 영향을 확인해주세요.
PageContainer
를 pageContainer
로 변경함에 따라 이를 임포트하는 다른 파일들의 수정이 필요할 수 있습니다.
다음 스크립트를 실행하여 영향을 받는 파일들을 확인하세요:
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (1)
23-34
: Props 인터페이스가 잘 구현되었습니다!
콜백 함수들이 옵셔널로 잘 정의되어 있으며, 네이밍 컨벤션도 잘 지켜졌습니다.
src/pages/BalanceGamePage/BalanceGamePage.tsx (2)
26-29
: useModal 훅의 북마크
useModal 훅을 잘 활용하여 모달 상태를 간소화하고 있습니다. 추가로, 모달 별로 useModal 훅을 재사용할 때 네이밍 충돌이 없도록 주의하면 좋을 것 같습니다.
50-52
: 수정 기능 라우팅 처리 확인
수정 버튼 클릭 시 경로를 correct하게 연결하고 있습니다. 다른 곳에서 동일한 라우트를 사용하거나 혹은 잘못된 path 호출이 없는지도 점검 부탁드립니다.
src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx (3)
20-39
: gameSet 미존재 시 처리 로직 제안
currentGameSetId로 조회했는데 gameSet이 없을 경우(404, 혹은 null), 화면에 적절한 안내를 띄우거나 에러 처리를 할 필요가 있어 보입니다.
51-52
: 이미지 관리 훅의 사용
handleOptionImageChange와 deleteImage 기능 분리가 명확하여 유지보수성이 좋아 보입니다. 다만, 에러 시 어떻게 처리될지 혹은 로딩 상태는 어떻게 표시되는지 확인 부탁드립니다.
125-137
: 이미지 변경 시 예외 상황 처리
비정상 파일 형식 혹은 파일 업로드 실패 시 사용자에게 안내가 필요한지 검토해 보세요.
src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx (3)
30-32
: 초기 스테이지 생성
createInitialGameStages(TOTAL_STAGE)를 람다로 감싸 useState 초기값으로 설정하여 렌더링 시마다 새 객체를 생성하지 않는 점은 좋습니다. 성능 측면에서도 적절해 보입니다.
156-160
: 주석 유지보수
현재 주석이 잘 작성되어 있습니다. 향후 로직 변경 시 주석이 코드와 일치하도록 지속 관리 부탁드립니다.
170-179
: onImageChange 반환값
onImageChange를 async로 선언하고 있어 반환값을 처리할 여지가 있는데, 실제 호출부에서 await를 사용하는지 확인 필요합니다.
src/hooks/modal/useModal.ts (1)
9-16
: 훅 구현이 깔끔합니다!
상태 관리가 효율적으로 구현되어 있고, 불필요한 리렌더링이 없습니다.
src/hooks/api/game/useUpdateGameMutation.ts (1)
6-9
: 타입 정의가 명확합니다
UpdateGameParams
인터페이스가 잘 정의되어 있습니다.
src/constants/path.ts (1)
23-25
: 밸런스 게임 수정 경로가 적절하게 추가되었습니다!
라우트 패턴과 링크 생성 함수가 기존 코드 스타일을 잘 따르고 있으며, 타입 안전성도 보장되어 있습니다.
src/components/organisms/BalanceGameCreation/BalanceGameCreation.style.ts (1)
41-43
: 컨테이너의 상대적 위치 지정이 적절합니다.
태그 편집 버튼의 위치 지정을 위한 기준점으로 잘 활용되고 있습니다.
src/pages/BalanceGamePage/BalanceGamePage.style.ts (1)
1-4
: 타이포그래피와 색상 상수 사용이 잘 되어있습니다.
디자인 시스템의 타이포그래피와 색상 상수를 일관성 있게 잘 활용하고 있습니다.
src/constants/message.ts (1)
104-108
: 성공 메시지가 일관성 있게 구성되었습니다.
게임 관련 메시지들이 GAME
객체 아래 잘 구조화되어 있습니다.
const handleBookmarkClick = () => { | ||
if (!game) return; | ||
|
||
if (isGuest) { | ||
setLoginModalOpen(true); | ||
return; | ||
} | ||
|
||
if (isMyGame) { | ||
showToastModal(ERROR.BOOKMARK.MY_GAME); | ||
return; | ||
} | ||
|
||
if (currentGame.myBookmark) { | ||
deleteBookmark(); | ||
} else { | ||
createBookmark(); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
북마크 성공 시 사용자 피드백 추가 필요
북마크 기능에 성공 케이스에 대한 피드백이 누락되어 있습니다:
if (currentGame.myBookmark) {
- deleteBookmark();
+ deleteBookmark();
+ showToastModal(SUCCESS.BOOKMARK.DELETED);
} else {
- createBookmark();
+ createBookmark();
+ showToastModal(SUCCESS.BOOKMARK.CREATED);
}
Committable suggestion skipped: line range outside the PR's diff.
const myGameItem: MenuItem[] = [ | ||
{ label: '수정', onClick: onEdit }, | ||
{ label: '삭제', onClick: onDelete }, | ||
]; | ||
const otherGameItem: MenuItem[] = [{ label: '신고', onClick: onReport }]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메뉴 아이템 콜백에 에러 처리 추가 필요
현재 메뉴 아이템의 콜백 함수들이 에러 처리 없이 직접 호출되고 있습니다. 다음과 같이 개선이 필요합니다:
const myGameItem: MenuItem[] = [
- { label: '수정', onClick: onEdit },
- { label: '삭제', onClick: onDelete },
+ {
+ label: '수정',
+ onClick: () => {
+ try {
+ onEdit?.();
+ } catch (error) {
+ showToastModal(ERROR.EDIT_FAILED);
+ console.error('게임 수정 중 오류 발생:', error);
+ }
+ }
+ },
+ {
+ label: '삭제',
+ onClick: () => {
+ try {
+ onDelete?.();
+ } catch (error) {
+ showToastModal(ERROR.DELETE_FAILED);
+ console.error('게임 삭제 중 오류 발생:', error);
+ }
+ }
+ },
];
Committable suggestion skipped: line range outside the PR's diff.
onImageChange={(file) => { | ||
void onImageChange(currentStage, 1, file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ESLint: no-void
동일하게, void 사용이 ESLint 룰에 의해 금지되어 있으므로 아래와 같이 수정 제안드립니다.
- void onImageChange(currentStage, 1, file);
+ onImageChange(currentStage, 1, file);
📝 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.
onImageChange={(file) => { | |
void onImageChange(currentStage, 1, file); | |
onImageChange={(file) => { | |
onImageChange(currentStage, 1, file); |
🧰 Tools
🪛 eslint
[error] 171-171: Expected 'undefined' and instead saw 'void'.
(no-void)
onImageChange={(file) => { | ||
void onImageChange(currentStage, 0, file); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ESLint: no-void
void 키워드 대신 undefined 반환을 의도하신 거라면, 간단히 즉시 실행하고 반환값을 사용하지 않도록 수정하시는 게 좋습니다.
- void onImageChange(currentStage, 0, file);
+ onImageChange(currentStage, 0, file);
📝 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.
onImageChange={(file) => { | |
void onImageChange(currentStage, 0, file); | |
onImageChange={(file) => { | |
onImageChange(currentStage, 0, file); |
🧰 Tools
🪛 eslint
[error] 153-153: Expected 'undefined' and instead saw 'void'.
(no-void)
onSuccess: async (_, { gameSetId }) => { | ||
await queryClient.invalidateQueries({ queryKey: ['gameSet', gameSetId] }); | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
에러 처리 개선 필요
쿼리 무효화 과정에서 발생할 수 있는 에러에 대한 처리가 필요합니다.
onSuccess: async (_, { gameSetId }) => {
- await queryClient.invalidateQueries({ queryKey: ['gameSet', gameSetId] });
+ try {
+ await queryClient.invalidateQueries({ queryKey: ['gameSet', gameSetId] });
+ } catch (error) {
+ console.error('Failed to invalidate queries:', error);
+ }
},
📝 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.
onSuccess: async (_, { gameSetId }) => { | |
await queryClient.invalidateQueries({ queryKey: ['gameSet', gameSetId] }); | |
}, | |
onSuccess: async (_, { gameSetId }) => { | |
try { | |
await queryClient.invalidateQueries({ queryKey: ['gameSet', gameSetId] }); | |
} catch (error) { | |
console.error('Failed to invalidate queries:', error); | |
} | |
}, |
onGamesChange: () => {}, | ||
onImageChange: async () => true, | ||
onImageDelete: () => {}, | ||
games: dummyGames, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
비동기 핸들러 에러 처리 필요
onImageChange
핸들러가 항상 true
를 반환하고 있어 실제 에러 상황을 테스트할 수 없습니다.
다음과 같이 개선할 것을 제안합니다:
args: {
title: '밸런스 게임 제목',
onTitleChange: () => {},
handleCompleteClick: () => {},
onGamesChange: () => {},
- onImageChange: async () => true,
+ onImageChange: async (file: File) => {
+ // 파일 크기나 타입에 따라 다른 결과 반환
+ return file.size <= 5000000;
+ },
onImageDelete: () => {},
games: dummyGames,
},
Committable suggestion skipped: line range outside the PR's diff.
export const deleteBySetId = async (gameSetId: Id) => { | ||
return axiosInstance.delete(END_POINT.GAME_SET(gameSetId)); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
삭제 API의 응답 처리를 명확히 해주세요.
삭제 API의 응답 처리가 불명확합니다. 성공/실패 여부를 명확하게 반환하도록 개선이 필요합니다.
-export const deleteBySetId = async (gameSetId: Id) => {
- return axiosInstance.delete(END_POINT.GAME_SET(gameSetId));
+export const deleteBySetId = async (gameSetId: Id): Promise<boolean> => {
+ try {
+ await axiosInstance.delete(END_POINT.GAME_SET(gameSetId));
+ return true;
+ } catch (error) {
+ console.error('게임 삭제 중 오류 발생:', error);
+ return false;
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const deleteBySetId = async (gameSetId: Id) => { | |
return axiosInstance.delete(END_POINT.GAME_SET(gameSetId)); | |
}; | |
export const deleteBySetId = async (gameSetId: Id): Promise<boolean> => { | |
try { | |
await axiosInstance.delete(END_POINT.GAME_SET(gameSetId)); | |
return true; | |
} catch (error) { | |
console.error('게임 삭제 중 오류 발생:', error); | |
return false; | |
} | |
}; |
src/api/game.ts
Outdated
export const updateGameBySetId = async ( | ||
gameSetId: Id, | ||
gameData: BalanceGame, | ||
) => { | ||
const { data } = await axiosInstance.put<BalanceGame>( | ||
END_POINT.GAME_SET(gameSetId), | ||
gameData, | ||
); | ||
return data; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
API 응답 타입과 에러 처리 개선이 필요합니다.
게임 수정 API의 응답 처리가 불완전해 보입니다. 에러 상황에서의 타입 안전성을 보장하기 위해 다음과 같은 개선이 필요합니다.
export const updateGameBySetId = async (
gameSetId: Id,
gameData: BalanceGame,
-) => {
+): Promise<BalanceGame | undefined> => {
+ try {
const { data } = await axiosInstance.put<BalanceGame>(
END_POINT.GAME_SET(gameSetId),
gameData,
);
return data;
+ } catch (error) {
+ console.error('게임 수정 중 오류 발생:', error);
+ return undefined;
+ }
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const updateGameBySetId = async ( | |
gameSetId: Id, | |
gameData: BalanceGame, | |
) => { | |
const { data } = await axiosInstance.put<BalanceGame>( | |
END_POINT.GAME_SET(gameSetId), | |
gameData, | |
); | |
return data; | |
}; | |
export const updateGameBySetId = async ( | |
gameSetId: Id, | |
gameData: BalanceGame, | |
): Promise<BalanceGame | undefined> => { | |
try { | |
const { data } = await axiosInstance.put<BalanceGame>( | |
END_POINT.GAME_SET(gameSetId), | |
gameData, | |
); | |
return data; | |
} catch (error) { | |
console.error('게임 수정 중 오류 발생:', error); | |
return undefined; | |
} | |
}; |
const handleImageUpload = async ( | ||
imageFile: File, | ||
type: FileUploadType, | ||
): Promise<{ imgUrl: string; fileId: number }> => { | ||
const resizedBlob = await resizeImage(imageFile, 577, 359); | ||
const resizedFile = new File([resizedBlob], imageFile.name, { | ||
type: imageFile.type, | ||
}); | ||
|
||
const formData = new FormData(); | ||
formData.append('file', resizedFile); | ||
|
||
const response = await uploadImage({ formData, params: type }); | ||
return { imgUrl: response.imgUrls[0], fileId: response.fileIds[0] }; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
이미지 업로드 처리 로직 개선이 필요합니다.
이미지 업로드 과정에서 파일 크기와 타입 검증이 누락되어 있습니다. 또한 에러 처리가 구체적이지 않습니다.
const handleImageUpload = async (
imageFile: File,
type: FileUploadType,
): Promise<{ imgUrl: string; fileId: number }> => {
+ // 파일 크기 검증 (예: 최대 5MB)
+ if (imageFile.size > 5 * 1024 * 1024) {
+ throw new Error('파일 크기는 5MB를 초과할 수 없습니다.');
+ }
+
+ // 이미지 타입 검증
+ if (!imageFile.type.startsWith('image/')) {
+ throw new Error('이미지 파일만 업로드 가능합니다.');
+ }
+
const resizedBlob = await resizeImage(imageFile, 577, 359);
const resizedFile = new File([resizedBlob], imageFile.name, {
type: imageFile.type,
});
const formData = new FormData();
formData.append('file', resizedFile);
const response = await uploadImage({ formData, params: type });
return { imgUrl: response.imgUrls[0], fileId: response.fileIds[0] };
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleImageUpload = async ( | |
imageFile: File, | |
type: FileUploadType, | |
): Promise<{ imgUrl: string; fileId: number }> => { | |
const resizedBlob = await resizeImage(imageFile, 577, 359); | |
const resizedFile = new File([resizedBlob], imageFile.name, { | |
type: imageFile.type, | |
}); | |
const formData = new FormData(); | |
formData.append('file', resizedFile); | |
const response = await uploadImage({ formData, params: type }); | |
return { imgUrl: response.imgUrls[0], fileId: response.fileIds[0] }; | |
}; | |
const handleImageUpload = async ( | |
imageFile: File, | |
type: FileUploadType, | |
): Promise<{ imgUrl: string; fileId: number }> => { | |
// 파일 크기 검증 (예: 최대 5MB) | |
if (imageFile.size > 5 * 1024 * 1024) { | |
throw new Error('파일 크기는 5MB를 초과할 수 없습니다.'); | |
} | |
// 이미지 타입 검증 | |
if (!imageFile.type.startsWith('image/')) { | |
throw new Error('이미지 파일만 업로드 가능합니다.'); | |
} | |
const resizedBlob = await resizeImage(imageFile, 577, 359); | |
const resizedFile = new File([resizedBlob], imageFile.name, { | |
type: imageFile.type, | |
}); | |
const formData = new FormData(); | |
formData.append('file', resizedFile); | |
const response = await uploadImage({ formData, params: type }); | |
return { imgUrl: response.imgUrls[0], fileId: response.fileIds[0] }; | |
}; |
src/App.tsx
Outdated
<Route | ||
path={PATH.BALANCEGAME_EDIT_ROUTE} | ||
element={<BalanceGameEditPage />} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인증된 사용자만 접근 가능하도록 수정이 필요합니다.
게임 수정 기능은 인증된 사용자만 접근할 수 있어야 합니다. 현재 라우트가 보호되지 않은 영역에 위치해 있습니다.
다음과 같이 보호된 라우트 영역으로 이동하는 것을 제안합니다:
- <Route
- path={PATH.BALANCEGAME_EDIT_ROUTE}
- element={<BalanceGameEditPage />}
- />
<Route element={<ProtectedRoutes isLoggedIn={isLoggedIn} />}>
<Route path={PATH.MYPAGE} element={<LayoutNoFooter />}>
<Route index element={<MyPage />} />
</Route>
<Route path="/" element={<Layout />}>
<Route path={PATH.CREATE.TALK_PICK} element={<CreatePostPage />} />
<Route
path={PATH.CREATE.GAME}
element={<BalanceGameCreationPage />}
/>
+ <Route
+ path={PATH.BALANCEGAME_EDIT_ROUTE}
+ element={<BalanceGameEditPage />}
+ />
<Route
path={PATH.CHANGE.PROFILE}
element={<ChangeUserInfoPage />}
/>
</Route>
</Route>
📝 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.
<Route | |
path={PATH.BALANCEGAME_EDIT_ROUTE} | |
element={<BalanceGameEditPage />} | |
/> | |
<Route element={<ProtectedRoutes isLoggedIn={isLoggedIn} />}> | |
<Route path={PATH.MYPAGE} element={<LayoutNoFooter />}> | |
<Route index element={<MyPage />} /> | |
</Route> | |
<Route path="/" element={<Layout />}> | |
<Route path={PATH.CREATE.TALK_PICK} element={<CreatePostPage />} /> | |
<Route | |
path={PATH.CREATE.GAME} | |
element={<BalanceGameCreationPage />} | |
/> | |
<Route | |
path={PATH.BALANCEGAME_EDIT_ROUTE} | |
element={<BalanceGameEditPage />} | |
/> | |
<Route | |
path={PATH.CHANGE.PROFILE} | |
element={<ChangeUserInfoPage />} | |
/> | |
</Route> | |
</Route> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/utils/balanceGameUtils.ts (1)
36-66
:⚠️ Potential issue데이터 변환 시 유효성 검사 강화 필요
데이터 변환 과정에서 예외 상황에 대한 처리가 부족합니다. 이전 리뷰에서 지적된 사항이 아직 반영되지 않았습니다.
다음과 같이 개선할 것을 제안합니다:
export const transformGameSetToBalanceGame = ( gameSet: GameSet, ): BalanceGameSet[] => { + if (!gameSet?.gameDetailResponses) { + throw new Error('유효하지 않은 게임 데이터입니다'); + } + return gameSet.gameDetailResponses.map( - ({ description = '', gameOptions }) => ({ + ({ description = '', gameOptions }) => { + if (!Array.isArray(gameOptions) || gameOptions.length !== 2) { + throw new Error('게임 옵션은 정확히 2개여야 합니다'); + } + return { description, gameOptions: gameOptions.map( ({ id, name, description: optionDescription, optionType, imgUrl, fileId, }) => { + if (!id || !name || !optionType) { + throw new Error('필수 게임 옵션 필드가 누락되었습니다'); + } + if (!['A', 'B'].includes(optionType)) { + throw new Error('옵션 타입은 A 또는 B여야 합니다'); + } return { id, name, description: optionDescription, optionType, - imgUrl: imgUrl ?? '', + imgUrl: imgUrl || '', // 빈 문자열이나 null 모두 처리 fileId: fileId ?? null, }; }, ), - }), + }), ); };추가로 다음 사항도 고려해보세요:
- try-catch로 전체 변환 과정을 감싸서 에러 처리를 일관되게 관리
- 변환된 데이터의 형식을 검증하는 별도의 유틸리티 함수 작성
- 에러 메시지를 상수로 분리하여 관리
🧹 Nitpick comments (1)
src/utils/balanceGameUtils.ts (1)
36-40
: JSDoc 문서 개선 필요현재 JSDoc에 중요한 정보가 누락되어 있습니다.
다음과 같이 개선할 것을 제안합니다:
/** * GameSet 데이터를 BalanceGameSet 형식으로 변환하는 함수 * @param gameSet GameSet API에서 받은 원본 데이터 * @returns BalanceGameSet[]로 가공된 데이터 + * @throws {Error} gameSet이 유효하지 않은 경우 + * @throws {Error} gameOptions가 2개가 아닌 경우 + * @throws {Error} 필수 필드가 누락된 경우 */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/utils/balanceGameUtils.ts
(2 hunks)
🔇 Additional comments (1)
src/utils/balanceGameUtils.ts (1)
1-1
: LGTM! 타입 임포트가 적절히 추가되었습니다.
GameSet 타입이 새로운 변환 함수에 필요하여 적절하게 추가되었습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
밸런스 게임 수정 로직 구현 수고 많으셨어요!!! 밸런스 게임 페이지로 이동되는 navigate 함수 경로들 상수로 처리 부탁드립니다!! 코멘트도 확인 부탁드려용🙌
src/constants/path.ts
Outdated
BALANCEGAME: (setId: number | string = ':setId') => `balancegame/${setId}`, | ||
BALANCEGAME_EDIT_ROUTE: 'balancegame/edit/:gameSetId', | ||
BALANCEGAME_EDIT_LINK: (gameSetId: number | string) => | ||
`balancegame/edit/${gameSetId}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위의 BALANCEGAME처럼 한번에 선언이 가능한데 두 가지로 분리하신 이유가 궁금합니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
경로 변경이나 유지보수를 할 때 좀 더 용이하게 관리하기 위해서 해당 방식을 시도해보았는데, 크게 복잡한 정도의 라우팅 처리는 아니기 때문에 BALANCEGAME 관련 라우팅은
BALANCEGAME: (setId: number | string = ':setId') => `balancegame/${setId}`, | |
BALANCEGAME_EDIT_ROUTE: 'balancegame/edit/:gameSetId', | |
BALANCEGAME_EDIT_LINK: (gameSetId: number | string) => | |
`balancegame/edit/${gameSetId}`, | |
export const BALANCEGAME = { | |
EDIT: 'balancegame/edit/:gameSetId', | |
CREATE: 'balancegame/create', | |
}; |
이런 식으로 그룹핑하는게 현재 규모에서는 더 적합해 보이네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가로, 정적 값과 동적 값을 동시에 사용한 이유는
동적 값: 실제 navigate 로직에서 사용
, 정적 값: App.tsx에서 라우팅 설정을 선언하기 위함
이었습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/api/game.ts
Outdated
export const updateGameBySetId = async ( | ||
gameSetId: Id, | ||
gameData: BalanceGame, | ||
) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
api 함수의 경우 함수명을 서버로의 요청인 post / put / delete로 시작하도록 통일하면 좋을 것 같아요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 함수를 사용하는 쿼리 훅도 useEdit~로 통일 부탁드려요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사실 네이밍의 경우에는 비즈니스 로직의 역할에 충실하는게 더 맞다고는 생각이 드는데요(후속되는 get post put 은 함수 내부에서 확인할 수도 있기 때문), 현재 대부분의 호출 함수들이 말씀해주신 방식으로 되어있다보니, 요청하신 방식으로 수정하도록 하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
const TOTAL_STAGE = 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상수 값은 contants/game.ts
에 정의하여 사용하는게 어떨까요?!?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 페이지에서만 사용하기 때문에 이러한 처리를 했는데, 확장성을 위해 969b9ca 에 처리하였습니다!
src/App.tsx
Outdated
<Route | ||
path={PATH.BALANCEGAME_EDIT_ROUTE} | ||
element={<BalanceGameEditPage />} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로그인 상태일 때 해당 페이지에 접근할 수 있도록 넣어두셨는데 혹시 본인이 작성한 게 아닌 게임의 수정 페이지에도 접근이 가능한 상태라면 수정이 필요해보입니다!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
url로도 해당 페이지에 접근 가능하다는 사실을 간과 하고 있었네요!
ce3fe30에서 해결했습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (12)
src/pages/BalanceGamePage/BalanceGamePage.tsx (5)
1-20
: 타입 안전성 개선을 위한 제안컴포넌트의 props 타입들을 명시적으로 import하면 타입 안전성이 향상될 것 같습니다.
다음과 같이 타입을 추가하는 것을 고려해보세요:
+import type { BalanceGameSectionProps } from '@/components/organisms/BalanceGameSection/BalanceGameSection'; +import type { BalanceGameEndingSectionProps } from '@/components/organisms/BalanceGameEndingSection/BalanceGameEndingSection';
22-41
: 에러 처리 강화 제안현재 구현은 잘 되어있지만, 에러 바운더리를 추가하면 더 안정적인 에러 처리가 가능할 것 같습니다.
다음과 같은 에러 바운더리 구현을 고려해보세요:
class BalanceGameErrorBoundary extends React.Component { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { // 에러 로깅 및 처리 } render() { return this.props.children; } }
42-49
: 매직 넘버 상수화 제안게임 스테이지 관련 매직 넘버를 상수로 분리하면 유지보수성이 향상될 것 같습니다.
다음과 같이 상수를 추가하는 것을 추천드립니다:
+const GAME_CONSTANTS = { + MIN_STAGE: 0, + MAX_STAGE: 10, +} as const; const handleNextGame = () => { - setCurrentStage((stage) => (stage < 10 ? stage + 1 : stage)); + setCurrentStage((stage) => (stage < GAME_CONSTANTS.MAX_STAGE ? stage + 1 : stage)); };
54-76
: 삭제 중 로딩 상태 처리 제안삭제 작업 중에 사용자에게 로딩 상태를 표시하면 더 나은 사용자 경험을 제공할 수 있을 것 같습니다.
다음과 같이 로딩 상태를 추가하는 것을 고려해보세요:
+const [isDeleting, setIsDeleting] = useState(false); const handleDeleteClick = () => { setModalProps({ text: '정말 삭제하시겠습니까?', onConfirm: () => { + setIsDeleting(true); deleteGameSet( { gameSetId }, { onSuccess: () => { + setIsDeleting(false); closeModal(); showToastModal(SUCCESS.GAME.DELETE); navigate('/'); }, onError: () => { + setIsDeleting(false); closeModal(); showToastModal(ERROR.DELETEGAME.FAIL); }, }, ); }, onClose: closeModal, }); openModal(); };
89-129
: 성능 최적화를 위한 메모이제이션 제안컴포넌트 리렌더링을 최적화하기 위해 메모이제이션을 적용하면 좋을 것 같습니다.
다음과 같이 메모이제이션을 적용하는 것을 고려해보세요:
+const MemoizedBalanceGameSection = React.memo(BalanceGameSection); +const MemoizedBalanceGameEndingSection = React.memo(BalanceGameEndingSection); return ( <div css={S.pageStyle}> {currentStage === 10 ? ( - <BalanceGameEndingSection + <MemoizedBalanceGameEndingSection title={gameSet?.title ?? ''} gameSetId={gameSetId} isMyGame={isMyGame} isMyEndBookmark={gameSet?.isEndBookmarked ?? false} /> ) : ( - <BalanceGameSection + <MemoizedBalanceGameSection gameSetId={gameSetId} game={gameSet} isMyGame={isMyGame} currentStage={currentStage} setCurrentStage={setCurrentStage} handleNextGame={handleNextGame} handlePrevGame={handlePrevGame} onEdit={handleEditClick} onDelete={handleDeleteClick} onReport={handleReportClick} /> )} </div> );src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx (1)
54-61
: [useEffect 내부에서 초기 값 설정 주의]
gameSet
값이 존재할 때setTitle
,setGames
,setMainTag
,setSubTag
등을 호출하는 것은 적절합니다. 다만, API 응답이 지연되어 늦게 세팅되는 경우 UI 깜빡임이 발생할 수 있으므로, 로딩 상태(isLoading 등)를 별도로 관리하면 사용자 경험 개선에 도움이 될 수 있습니다.src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx (2)
72-90
: [validateStage 함수 검증 로직 적절]
게임 옵션의 이름과 이미지를 검사해 유효성을 체크하는 방식이 명확해 보입니다. 다만, 확장될 수 있는 검증 항목(예: 문자열 길이, 특정 규칙 등)에 대비해 조건을 추가하기 쉽게 재사용 가능한 헬퍼 함수로 분리해두면 유지보수에 유리할 듯합니다.
127-146
: [태그 수정 버튼 표시 조건 간소화 가능]
handleTagEditClick
가 존재할 때만 버튼을 렌더링하는 로직은 올바릅니다. 필요하다면props
단에 기본 함수를 제공하여 조건부 분기를 제거할 수도 있습니다. 현재 구현 그대로도 충분히 명확하므로 선택 사항으로 참고하시기 바랍니다.src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx (4)
50-53
: 타입 정의를 더 명확하게 개선해보세요.
popupData
의 타입을 별도의 인터페이스로 분리하여 재사용성을 높이고 의도를 더 명확하게 표현할 수 있습니다.+interface ImageDeletePopupData { + stageIndex: number; + optionIndex: number; +} -const [popupData, setPopupData] = useState<{ - stageIndex: number; - optionIndex: number; -} | null>(null); +const [popupData, setPopupData] = useState<ImageDeletePopupData | null>(null);
80-88
: 에러 처리를 개선해보세요.이미지 삭제 과정에서 발생할 수 있는 예외 상황에 대한 처리가 필요합니다.
const handleConfirmDeleteImage = () => { if (popupData) { - deleteImage(popupData.stageIndex, popupData.optionIndex, (updater) => { - setGames(updater); - }); - showToastModal(SUCCESS.IMAGE.DELETE); + try { + deleteImage(popupData.stageIndex, popupData.optionIndex, (updater) => { + setGames(updater); + }); + showToastModal(SUCCESS.IMAGE.DELETE); + } catch (error) { + showToastModal(ERROR.IMAGE.DELETE); + } } setPopupData(null); closeTextModal(); };
Line range hint
114-122
: 에러 타입을 구체화하여 처리해보세요.에러 타입에 따라 다른 메시지를 표시하면 사용자에게 더 명확한 피드백을 제공할 수 있습니다.
try { const gameId = await handleCreateBalanceGame(gameData); showToastModal(SUCCESS.GAME.CREATE); navigate(`/balancegame/${gameId}`); } catch (error) { - showToastModal(ERROR.CREATEGAME.FAIL); + if (error instanceof NetworkError) { + showToastModal(ERROR.NETWORK); + } else if (error instanceof ValidationError) { + showToastModal(ERROR.VALIDATION); + } else { + showToastModal(ERROR.CREATEGAME.FAIL); + } }
155-162
: JSDoc 문서를 더 상세하게 작성해보세요.매개변수와 반환값에 대한 설명을 추가하면 함수의 사용 방법을 더 명확하게 이해할 수 있습니다.
/** * 상태 변경 요청을 처리하는 콜백 * 자식이 상태를 갱신하고자 할 때 이 함수를 호출하면 * 부모에서 setGames를 통해 상태를 갱신 + * @param updatedGames - 갱신할 게임 상태 배열 + * @returns void */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/App.tsx
(3 hunks)src/api/game.ts
(1 hunks)src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx
(3 hunks)src/components/organisms/BalanceGameList/BalanceGameList.tsx
(2 hunks)src/constants/game.ts
(1 hunks)src/constants/path.ts
(1 hunks)src/hooks/api/game/useEditGamesMutation.ts
(1 hunks)src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx
(7 hunks)src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx
(1 hunks)src/pages/BalanceGamePage/BalanceGamePage.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/App.tsx
- src/constants/path.ts
- src/api/game.ts
🔇 Additional comments (8)
src/components/organisms/BalanceGameList/BalanceGameList.tsx (2)
11-11
: PATH 상수 도입으로 라우팅 유지보수성 향상
PATH 상수를 통해 경로를 일관성 있게 관리하므로, 추후 라우트 변경 시 수정 범위가 최소화되어 유지보수에 유리합니다.
40-40
: PATH 상수 활용을 통한 동적 경로 구성
기존에 문자열로 직접 경로를 작성하던 방식을 PATH 상수로 대체하여, 경로 정의를 모듈화하고 중복을 줄일 수 있습니다.
src/constants/game.ts (1)
6-6
: [TOTAL_STAGE 상수 선언]
새로운 상수를 추가하여 반복되는 값을 제거한 점이 좋습니다. 다만, 추후 게임 단계가 동적으로 변동될 필요가 있다면 환경 변수나 설정 파일에서 관리하는 것도 고려해보세요.
src/hooks/api/game/useEditGamesMutation.ts (2)
6-9
: [UpdateGameParams 인터페이스 잘 정의됨]
UpdateGameParams
인터페이스에서 gameSetId
와 data
를 명확히 분리해 타입 안정성을 높였습니다. 현재 구조로는 충분히 가독성이 좋으며, 확장성도 우수해 보입니다.
11-20
: [useEditGamesMutation 훅 설계 간단명료]
react-query
를 활용해 수정 로직을 훅으로 잘 추상화하셨습니다. onSuccess
시 invalidateQueries
를 통해 최신 데이터를 보장하는 부분도 깔끔합니다. 에러 상황에 대한 추가 처리(예: 실패 토스트 노출 등) 역시 별도의 콜백으로 필요에 따라 확장할 수 있어 보입니다.
src/pages/BalanceGameEditPage/BalanceGameEditPage.tsx (1)
71-91
: [handleSaveEdit: 게임 수정 로직 효율적]
mutate
호출 시 onSuccess
, onError
콜백을 명시적으로 전달하여, 성공/실패 시나리오를 각각 처리하고 있습니다. 이 패턴은 추적이 용이하고 코드 가독성도 높여주므로 바람직합니다.
src/components/organisms/BalanceGameCreation/BalanceGameCreation.tsx (1)
152-154
: [onImageChange 비동기 처리 확인]
현재 onImageChange
가 Promise<boolean>
을 반환하므로, 호출부에서 비동기 완료 후 추가적인 로직(에러 처리 등)을 진행할 수 있습니다. 이 반환값에 대한 후속 처리가 필요하다면, await onImageChange(...)
패턴을 사용하는 등 명시적으로 활용하길 권장합니다.
src/pages/BalanceGameCreationPage/BalanceGameCreationPage.tsx (1)
21-31
: 코드 모듈화가 잘 되어있습니다! 👍
커스텀 훅과 상수를 활용하여 코드를 모듈화하고 재사용성을 높였습니다.
const handleReportClick = () => { | ||
setModalProps({ | ||
text: '정말 신고하시겠습니까?', | ||
onConfirm: () => { | ||
closeModal(); | ||
}, | ||
onClose: closeModal, | ||
}); | ||
openModal(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
밸런스 게임 신고 API 구현 누락
현재 코드베이스에는 댓글 신고 기능(postCommentReport
)만 구현되어 있고, 밸런스 게임 신고를 위한 API가 구현되어 있지 않습니다. 다음 사항들이 필요합니다:
src/api/report.ts
에 밸런스 게임 신고를 위한 API 엔드포인트 구현- 해당 API를 사용하는 mutation hook 구현 (
useGameReportMutation
또는useBalanceReportMutation
) BalanceGamePage.tsx
의handleReportClick
함수에 실제 신고 로직 연동
🔗 Analysis chain
신고 API 구현 필요
신고 기능이 아직 완전히 구현되지 않은 것 같습니다. PR 목표에 언급된 대로 신고 API를 통합해야 합니다.
신고 API 구현이 필요합니다. 다음 스크립트로 현재 구현 상태를 확인할 수 있습니다:
Let me check the report API implementation further.
Let me check one final thing about the game report API implementation.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for report API implementation
# Search for report API related files
echo "Searching for report API implementation..."
rg -g "*.ts" -g "*.tsx" "report.*api|api.*report"
# Search for report mutation hooks
echo "Searching for report mutation hooks..."
rg -g "*.ts" -g "*.tsx" "useReport.*Mutation"
Length of output: 829
Script:
#!/bin/bash
# Check the implementation details of report API and mutations
# Check report API implementation
echo "Checking report API implementation..."
rg -g "*.ts" -g "*.tsx" "postCommentReport" -A 5
# Check if there's any game report API
echo "Checking game report API..."
fd "api" --type f --exec rg "game.*report|report.*game"
# Check the actual implementation of useReportCommentMutation
echo "Checking report mutation implementation..."
cat src/hooks/api/report/useReportCommentMutation.ts
Length of output: 3116
Script:
#!/bin/bash
# Check for game report API implementation
echo "Checking game report API implementation..."
cat src/api/report.ts
# Check for any other report-related mutations
echo "Checking for game report mutations..."
fd "hooks" --type f --exec rg "useGameReport|useBalanceReport"
Length of output: 713
const handleImageDelete = (stageIndex: number, optionIndex: number) => { | ||
setPopupData({ stageIndex, optionIndex }); | ||
openTextModal(); | ||
}; | ||
|
||
const handleConfirmDeleteImage = () => { | ||
if (popupData) { | ||
deleteImage(popupData.stageIndex, popupData.optionIndex, (updater) => { | ||
setGames(updater); | ||
}); | ||
showToastModal(SUCCESS.IMAGE.DELETE); | ||
} | ||
setPopupData(null); | ||
closeTextModal(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
[이미지 삭제 시 예외 처리 보완 권장]
deleteImage
후 결과 핸들링은 정상적으로 보이지만, 삭제 요청이 실패할 경우(네트워크 오류 등)에 대한 예외 처리가 보완되면 좋겠습니다. 작성된 showToastModal
로 상황을 알리거나 재시도 메커니즘을 추가해 사용자 혼선을 줄일 수 있습니다.
const handleImageChange = async ( | ||
stageIndex: number, | ||
optionIndex: number, | ||
file: File, | ||
) => { | ||
return onImageChange(stageIndex, optionIndex, file, (updater) => { | ||
setGames(updater); | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
파일 유효성 검사를 추가해보세요.
이미지 파일의 크기, 형식 등을 검증하여 잘못된 파일 업로드를 방지할 수 있습니다.
const handleImageChange = async (
stageIndex: number,
optionIndex: number,
file: File,
) => {
+ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
+ const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
+
+ if (file.size > MAX_FILE_SIZE) {
+ showToastModal(ERROR.IMAGE.SIZE_EXCEED);
+ return;
+ }
+
+ if (!ALLOWED_TYPES.includes(file.type)) {
+ showToastModal(ERROR.IMAGE.INVALID_TYPE);
+ return;
+ }
+
return onImageChange(stageIndex, optionIndex, file, (updater) => {
setGames(updater);
});
};
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (1)
137-141
:⚠️ Potential issue메뉴 아이템 콜백에 에러 처리가 필요합니다.
수정 및 삭제 작업 중 발생할 수 있는 오류에 대한 처리가 누락되어 있습니다.
이전 리뷰 피드백과 동일하게 다음과 같이 에러 처리를 추가해주세요:
const myGameItem: MenuItem[] = [ - { label: '수정', onClick: onEdit }, - { label: '삭제', onClick: onDelete }, + { + label: '수정', + onClick: () => { + try { + onEdit?.(); + } catch (error) { + showToastModal(ERROR.EDIT_FAILED); + console.error('게임 수정 중 오류 발생:', error); + } + } + }, + { + label: '삭제', + onClick: () => { + try { + onDelete?.(); + } catch (error) { + showToastModal(ERROR.DELETE_FAILED); + console.error('게임 삭제 중 오류 발생:', error); + } + } + }, ];
🧹 Nitpick comments (2)
src/pages/BalanceGamePage/BalanceGamePage.tsx (1)
80-80
: 불필요한 공백을 제거해주세요.줄 끝의 불필요한 공백을 제거해주세요.
🧰 Tools
🪛 eslint
[error] 80-80: Delete
····
(prettier/prettier)
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (1)
1-22
: 사용하지 않는 import 구문을 정리해주세요.다음 import들이 사용되지 않고 있습니다:
ERROR
from '@/constants/message'useCreateGameBookmarkMutation
useDeleteGameBookmarkMutation
MyVoteOption
VoteOption
-import { ERROR, SUCCESS } from '@/constants/message'; +import { SUCCESS } from '@/constants/message'; -import { useCreateGameBookmarkMutation } from '@/hooks/api/bookmark/useCreateGameBookmarkMutation'; -import { useDeleteGameBookmarkMutation } from '@/hooks/api/bookmark/useDeleteGameBookmarkMutation'; -import { MyVoteOption, VoteOption, VoteRecord } from '@/types/vote'; +import { VoteRecord } from '@/types/vote';🧰 Tools
🪛 eslint
[error] 3-3: 'ERROR' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 17-17: 'useCreateGameBookmarkMutation' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 18-18: 'useDeleteGameBookmarkMutation' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 21-21: 'MyVoteOption' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 21-21: 'VoteOption' is defined but never used.
(@typescript-eslint/no-unused-vars)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/App.tsx
(3 hunks)src/api/game.ts
(1 hunks)src/components/organisms/BalanceGameList/BalanceGameList.tsx
(2 hunks)src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
(1 hunks)src/constants/message.ts
(2 hunks)src/pages/BalanceGamePage/BalanceGamePage.tsx
(1 hunks)src/types/game.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- src/components/organisms/BalanceGameList/BalanceGameList.tsx
- src/types/game.ts
- src/api/game.ts
🧰 Additional context used
🪛 Biome (1.9.4)
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
[error] 68-71: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🪛 eslint
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
[error] 3-3: 'ERROR' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 17-17: 'useCreateGameBookmarkMutation' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 18-18: 'useDeleteGameBookmarkMutation' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 21-21: 'MyVoteOption' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 21-21: 'VoteOption' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 66-66: Delete ····
(prettier/prettier)
[error] 68-71: Unexpected empty object pattern.
(no-empty-pattern)
[error] 68-71: Delete ·⏎··⏎··⏎··
(prettier/prettier)
[error] 127-127: Delete ··
(prettier/prettier)
[error] 246-246: Delete ·
(prettier/prettier)
src/pages/BalanceGamePage/BalanceGamePage.tsx
[error] 80-80: Delete ····
(prettier/prettier)
🔇 Additional comments (6)
src/constants/message.ts (2)
59-64
: 메시지 상수가 잘 정의되어 있습니다!
게임 수정 및 삭제 관련 에러 메시지가 기존 상수들과 일관된 패턴으로 잘 정의되어 있습니다.
106-114
: 성공 메시지가 잘 구조화되어 있습니다!
게임 및 태그 관련 성공 메시지가 체계적으로 구조화되어 있습니다.
src/pages/BalanceGamePage/BalanceGamePage.tsx (2)
70-79
: 신고 기능 구현이 필요합니다.
신고 기능이 아직 완전히 구현되지 않았습니다. 모달만 표시되고 실제 신고 처리가 이루어지지 않고 있습니다.
40-68
: 삭제 기능이 잘 구현되어 있습니다!
게임 삭제 기능이 다음과 같이 잘 구현되어 있습니다:
- 삭제 확인 모달 표시
- 성공/실패 처리
- 적절한 에러 핸들링
- 삭제 후 홈 페이지로 이동
src/App.tsx (1)
111-114
: 라우팅이 올바르게 구현되어 있습니다!
밸런스 게임 수정 페이지가 ProtectedRoutes
내부에 위치하여 인증된 사용자만 접근 가능하도록 잘 구현되어 있습니다.
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (1)
24-34
: 인터페이스 변경이 잘 구현되었습니다!
수정, 삭제, 신고 기능을 위한 콜백 props가 적절하게 추가되었습니다.
const { | ||
|
||
|
||
} = useGuestGameVote( | ||
guestVotedList, | ||
setGuestVotedList, | ||
gameSetId, | ||
currentStage, | ||
game, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useGuestGameVote 훅의 반환값이 사용되지 않고 있습니다.
훅을 호출하고 있지만 반환값을 구조분해할당하지 않고 있습니다. 이는 잠재적인 버그가 될 수 있습니다.
다음과 같이 필요한 값들을 추가해주세요:
- const {
-
-
- } = useGuestGameVote(
+ const { handleGuestGameVote } = useGuestGameVote(
guestVotedList,
setGuestVotedList,
gameSetId,
currentStage,
game,
);
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Biome (1.9.4)
[error] 68-71: Unexpected empty object pattern.
(lint/correctness/noEmptyPattern)
🪛 eslint
[error] 68-71: Unexpected empty object pattern.
(no-empty-pattern)
[error] 68-71: Delete ·⏎··⏎··⏎··
(prettier/prettier)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/pages/BalanceGamePage/BalanceGamePage.tsx (1)
70-79
:⚠️ Potential issue밸런스 게임 신고 API 구현 필요
신고 기능이 아직 구현되지 않았습니다. 이전 리뷰에서 지적된 사항이 아직 해결되지 않았습니다.
신고 기능 구현을 위해 다음 작업이 필요합니다:
- 신고 API 엔드포인트 구현
useGameReportMutation
훅 구현handleReportClick
함수에 실제 신고 로직 연동
🧹 Nitpick comments (3)
src/pages/BalanceGamePage/BalanceGamePage.tsx (3)
1-20
: 타입 안전성 향상을 위한 제안컴포넌트의 props 타입과 API 응답 타입을 명시적으로 import하면 타입 안전성이 향상될 것 같습니다. 다음과 같은 타입들의 import를 추가하는 것을 고려해보세요:
GameSet
타입Member
타입DeleteGameSetMutationResponse
타입
81-83
: 매직 넘버 상수화 필요스테이지 관련 매직 넘버(0, 10)를 의미 있는 상수로 추출하면 코드의 가독성과 유지보수성이 향상될 것 같습니다.
+const MIN_STAGE = 0; +const MAX_STAGE = 10; const changeStage = (step: number) => { - setCurrentStage((stage) => Math.min(10, Math.max(0, stage + step))); + setCurrentStage((stage) => Math.min(MAX_STAGE, Math.max(MIN_STAGE, stage + step))); };
85-124
: 에러 바운더리 추가 권장컴포넌트에서 발생할 수 있는 예외 상황(데이터 로딩 실패, 게임 세트 없음 등)을 처리하기 위한 에러 바운더리 추가를 권장드립니다.
class BalanceGameErrorBoundary extends React.Component { // 에러 바운더리 구현 // 게임 로딩 실패, 존재하지 않는 게임 등의 상황 처리 }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
(1 hunks)src/pages/BalanceGamePage/BalanceGamePage.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/organisms/BalanceGameSection/BalanceGameSection.tsx
const BalanceGamePage = () => { | ||
const { setId } = useParams<{ setId: string }>(); | ||
const gameSetId = Number(setId); | ||
const navigate = useNavigate(); | ||
const { isOpen, openModal, closeModal } = useModal(); | ||
const [modalProps, setModalProps] = useState<Omit<TextModalProps, 'isOpen'>>({ | ||
text: '', | ||
}); | ||
|
||
const { gameSet } = useGameBySetId(gameSetId); | ||
const [currentStage, setCurrentStage] = useState<number>(0); | ||
|
||
const accessToken = useNewSelector(selectAccessToken); | ||
const { member } = useMemberQuery(useParseJwt(accessToken).memberId); | ||
const { isVisible, modalText, showToastModal } = useToastModal(); | ||
|
||
const isMyGame: boolean = member?.nickname === gameSet?.member; | ||
|
||
const { mutate: deleteGameSet } = useDeleteGameSetMutation(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
게임 세트 ID 유효성 검사 추가 필요
setId
가 유효하지 않은 경우(NaN)에 대한 처리가 필요합니다. 사용자가 잘못된 URL을 입력했을 때 적절한 에러 처리나 리다이렉션이 이루어져야 합니다.
const gameSetId = Number(setId);
+if (isNaN(gameSetId)) {
+ navigate('/404');
+ return null;
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const BalanceGamePage = () => { | |
const { setId } = useParams<{ setId: string }>(); | |
const gameSetId = Number(setId); | |
const navigate = useNavigate(); | |
const { isOpen, openModal, closeModal } = useModal(); | |
const [modalProps, setModalProps] = useState<Omit<TextModalProps, 'isOpen'>>({ | |
text: '', | |
}); | |
const { gameSet } = useGameBySetId(gameSetId); | |
const [currentStage, setCurrentStage] = useState<number>(0); | |
const accessToken = useNewSelector(selectAccessToken); | |
const { member } = useMemberQuery(useParseJwt(accessToken).memberId); | |
const { isVisible, modalText, showToastModal } = useToastModal(); | |
const isMyGame: boolean = member?.nickname === gameSet?.member; | |
const { mutate: deleteGameSet } = useDeleteGameSetMutation(); | |
const BalanceGamePage = () => { | |
const { setId } = useParams<{ setId: string }>(); | |
const gameSetId = Number(setId); | |
if (isNaN(gameSetId)) { | |
navigate('/404'); | |
return null; | |
} | |
const navigate = useNavigate(); | |
const { isOpen, openModal, closeModal } = useModal(); | |
const [modalProps, setModalProps] = useState<Omit<TextModalProps, 'isOpen'>>({ | |
text: '', | |
}); | |
const { gameSet } = useGameBySetId(gameSetId); | |
const [currentStage, setCurrentStage] = useState<number>(0); | |
const accessToken = useNewSelector(selectAccessToken); | |
const { member } = useMemberQuery(useParseJwt(accessToken).memberId); | |
const { isVisible, modalText, showToastModal } = useToastModal(); | |
const isMyGame: boolean = member?.nickname === gameSet?.member; | |
const { mutate: deleteGameSet } = useDeleteGameSetMutation(); |
Quality Gate passedIssues Measures |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정까지 고생 많으셨습니다!! 🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이미 아름님께서 피드백을 한참 주신뒤였군요.. 상수 수정만 작성해두었어요..! 구현하시느라 수고 많으셨습니다👏
@@ -19,7 +19,11 @@ export const PATH = { | |||
}, | |||
TODAY_TALKPICK: 'todaytalkpick', | |||
TALKPICK_PLACE: 'talkpickplace', | |||
BALANCEGAME: (setId: number | string = ':setId') => `balancegame/${setId}`, | |||
BALANCEGAME: { | |||
VIEW: (setId: number | string = ':setId') => `balancegame/${setId}`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분 머지되면 저도 수정해야겠네용!!
navigate(`/balancegame/${gameId}`); | ||
}); | ||
showToastModal(SUCCESS.GAME.CREATE); | ||
navigate(`/balancegame/${gameId}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분도 BALANCEGAME.VIEW(gameId)로 수정하셔도 될 거 같습니당
{ | ||
onSuccess: () => { | ||
showToastModal(SUCCESS.GAME.EDIT); | ||
navigate(`/balancegame/${currentGameSetId}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위와 동일하게 처리하면 어떨까용
💡 작업 내용
💡 자세한 설명
✅ 밸런스 게임 수정, 생성 시 공통 로직
이미지 추가, 삭제
는 밸런스게임 수정, 그리고 생성 시에 공통적으로 사용되는 부분이라고 생각합니다. 따라서 상태관리는 각 페이지에서 수행하되, 이미지 생성 그리고 삭제하는 로직은 공통 훅을 통해 코드의 중복 사용을 제어하였습니다.스타일 파일 역시 중복 처리되는 부분이 많기 때문에 수정 페이지는 생성 페이지의 스타일 파일을 그대로 import 해서 사용합니다.
✅ 밸런스 게임 생성(및 수정) 시 games 배열의 상태 관리
✅ 밸런스 게임 수정 로직
밸런스 게임 조회 api를 호출
후에 해당 데이터를 수정 인터페이스(생성과 동일)에 맞춰 가공 후 ui를 랜더링합니다.기본적인 수정 로직(imgUrl은 api가 수정이 되어야함)
default.mp4
상단 태그 선택 버튼 클릭 시
default.mp4
마지막 스테이지에서 태그 선택(버튼의 label 명은 수정하기 까다로워 일단 보류) 클릭 시
default.mp4
✅ 모달 공용 훅 생성
📗 참고 자료 (선택)
📢 리뷰 요구 사항 (선택)
🚩 후속 작업 (선택)
준수님께 요청드린 밸런스 게임 조회 시 fileId 값도 같이 반환되는 수정 사항 반영 후 수정 페이지에서 이미지가 정상적으로 로드 되는지 확인 필요!! (구현은 되어있음)
밸런스 게임 삭제 시 올바른 요청을 보냈는데도 500이 반환되는 문제 확인 필요.
밸런스 게임 신고 api는 아직 미구현
✅ 셀프 체크리스트
closes #231
Summary by CodeRabbit
릴리즈 노트
새로운 기능
버그 수정
개선 사항
기타