Skip to content
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: 밸런스게임 조회 모바일 페이지 ui 구현 및 api 연동 #267

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from

Conversation

areumH
Copy link
Collaborator

@areumH areumH commented Dec 18, 2024

💡 작업 내용

  • 밸런스게임 조회 모바일 컴포넌트 및 페이지 ui 구현
  • 밸런스게임 조회 모바일 페이지에 api 연동
  • 밸런스게임 투표, 북마크 훅 분리
  • 디바이스가 모바일일 경우 모바일 페이지가 보여지도록 수정

💡 자세한 설명

✅ useBalanceGameVote, useBalanceGameBookmark

  • 웹 컴포넌트와 모바일 컴포넌트에서 중복으로 사용되는 게임 투표, 북마크 기능을 훅으로 분리하여 사용하도록 수정했습니다.
  • 투표의 경우는 회원 / 비회원일 경우를 별도로 선언하였고, 북마크의 경우는 게임 단건에 대한 북마크, 엔딩 페이지에서의 북마크를 별도로 선언하였습니다.

✅ BalanceGameSection, BalanceGameEndingSection

  • 해당 컴포넌트의 height를 px의 숫자 단위로 고정시키면 어떤 디바이스로 페이지를 여느냐에 따라 이전, 다음 버튼 아래에 간격이 생길 수도 있고, height 길이보다 디바이스 화면의 세로 길이가 더 짧은 경우엔 페이지를 스크롤해야 이전, 다음 버튼이 보일 수도 있다고 생각되어 height가 화면의 전체 길이에서 헤더와 padding 길이를 뺀 만큼의 길이로 계산되도록 하였습니다.
  • +) 최대 길이는 800px로 지정해두었습니다!!
-.Clipchamp.5.mp4

테스트를 위해 헤더만 지우고 녹화했습니다!! 각각 웹 / 모바일 환경에서의 헤더 길이만큼 padding이 적용된 화면입니다 😊

📗 참고 자료 (선택)

📢 리뷰 요구 사항 (선택)

🚩 후속 작업 (선택)

✅ 셀프 체크리스트

  • PR 제목을 형식에 맞게 작성했나요?
  • 브랜치 전략에 맞는 브랜치에 PR을 올리고 있나요? (master/main이 아닙니다.)
  • 이슈는 close 했나요?
  • Reviewers, Labels, Projects를 등록했나요?
  • 작업 도중 문서 수정이 필요한 경우 잘 수정했나요?
  • 테스트는 잘 통과했나요?
  • 불필요한 코드는 제거했나요?

closes #266

Summary by CodeRabbit

  • 새로운 기능

    • 모바일 페이지에 대한 새로운 컴포넌트 추가.
    • 반응형 웹 디자인을 위한 뷰포트 메타 태그 추가.
    • 투표 기능을 위한 새로운 버튼 및 박스 컴포넌트 추가.
    • 게임 종료 섹션 및 게임 섹션을 위한 새로운 스타일 정의.
    • 사용자 및 게스트 투표 관리를 위한 새로운 훅 추가.
    • Storybook 구성을 통해 컴포넌트 테스트 및 시각화 용이.
  • 버그 수정

    • 버튼 크기 조정 및 스타일 개선.
  • 문서화

    • Storybook 구성 추가로 컴포넌트 테스트 및 시각화 용이.

@areumH areumH added ✔︎pull requests pull requests 코드 체크 요청 👩🏻‍💻 frontend 프론트엔드 작업 🎨 markup ✅feature labels Dec 18, 2024
@areumH areumH requested review from WonJuneKim and alwubin December 18, 2024 10:51
@areumH areumH self-assigned this Dec 18, 2024
Copy link

coderabbitai bot commented Dec 18, 2024

개요

Walkthrough

이 풀 리퀘스트는 밸런스 게임의 모바일 페이지 UI를 구현하고 API와 연동하는 작업을 포함합니다. 주요 변경 사항은 모바일 환경에 최적화된 새로운 컴포넌트와 후크, 라우팅 로직 추가, 그리고 반응형 디자인을 위한 스타일 조정으로 구성됩니다. 모바일 사용자 경험을 개선하기 위해 다양한 컴포넌트와 후크가 새롭게 도입되었습니다.

Changes

파일 변경 요약
public/index.html 뷰포트 메타 태그 추가
src/App.tsx 모바일 라우팅 로직 및 isMobile 후크 통합
src/assets/index.ts 새로운 SVG 아이콘 (RedCheckIcon, BlueCheckIcon) 추가
src/components/mobile/* 모바일 밸런스 게임 컴포넌트 및 스타일 추가
src/hooks/common/useIsMobile.ts 모바일 디바이스 감지 후크 추가
src/hooks/game/* 게임 북마크 및 투표 관련 후크 추가
src/pages/mobile/BalanceGameMobilePage/* 모바일 밸런스 게임 페이지 구현

Assessment against linked issues

목표 완료 여부 설명
밸런스게임 조회 모바일 컴포넌트 및 페이지 UI 구현 모바일 전용 컴포넌트와 페이지 구현 완료
밸런스게임 조회 모바일 페이지에 API 연동 API 연동 관련 변경 사항이 포함되지 않음.

Possibly related PRs

Suggested labels

✨feature

Suggested reviewers

  • alwubin
  • WonJuneKim

Poem

🐰 모바일의 세계로 뛰어든 토끼,
반응형 게임판에 춤을 추네
작은 화면에 큰 재미를 담아
밸런스 게임의 새로운 장을 열어
코드로 그리는 즐거운 모바일 여정! 🎮


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🧹 Nitpick comments (27)
src/hooks/game/useBalanceGameVote.ts (1)

38-61: 게스트 투표 로직 확인
게스트 유저의 투표, 취소, 교체 로직을 배열 인덱스를 찾아 적절히 수정해주는 구조가 명확합니다. 다만, findIndex로 찾았을 때 없을 경우( return -1 )에 대한 추가 체크가 필요할 수 있습니다.

가능하다면 currentVoteIndex가 -1일 때의 에러 처리를 명시적으로 추가하는 것이 안전합니다.

+ if (currentVoteIndex === -1 && selectedOption) {
+   // 혹은 별도 처리 로직
+ }
src/components/molecules/BalanceGameEndingBox/BalanceGameEndingBox.tsx (1)

11-11: useGameEndBookmark 훅 사용
useGameEndBookmark를 사용하여 즐겨찾기(북마크) 로직을 단순화시킨 것은 유지보수성에 긍정적입니다. API 연동 및 에러 처리가 훅 내부에서 일관성 있게 처리되는지 재차 점검해 주세요.

src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (3)

42-50: 컴포넌트 Props 설계
gameSetId, game, isMyGame 등 주요 상태를 명시적으로 전달받는 구조가 명확합니다. 후속 유지보수 시 props 증가 가능성이 높은 부분이니, 필요하다면 별도의 context나 상위 컴포넌트 분리도 고려할 수 있습니다.


61-67: useGuestGameVote 훅 연동
guestVotedList의 상태와 함수를 훅으로 분리함으로써 로직 가독성이 향상되었습니다. 다만, 훅 내부에서 발생할 수 있는 에러 케이스(잘못된 currentStage 등)에 대한 방어 로직이 필요할 수 있습니다.


123-130: handleBookmarkClick 파라미터 확인
콜백 함수를 통해 로그인 모달을 열어주는 형태는 유연도가 높습니다. 만약 비로그인에 대한 다른 처리가 필요해질 경우를 대비해, 콜백 이외의 로그인 리다이렉션 전략 등도 고려할 수 있겠습니다.

src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (3)

19-20: useGuestGameVote 훅 도입
guest 투표 로직 분야가 명확히 분리되어 가독성이 좋아졌습니다. 가능하다면 회원 투표 로직과 함께 유사한 Hook 시그니처를 유지하여, 이후 확장성 및 재사용성을 높이는 것이 좋겠습니다.


123-130: useGameBookmark 훅 사용
동일한 훅을 웹/모바일에서 공유하여 즐겨찾기 로직을 일원화한 점이 훌륭합니다. 북마크 생성/삭제에 대한 에러 상황(예: 네트워크 오류, 이미 북마크되어 있는 게임)에 대한 사용자 알림 처리도 곁들여 주시면 좋겠습니다.


239-239: handleBookmarkClick 콜백 인자
콜백을 통해 비로그인 시 별도의 모달을 호출하는 로직이 유연하게 처리됩니다. 다만, 로그인 처리 후 재시도 로직이 필요한 상황에 대비하여 handleBookmarkClick 내부에서 다시 한 번 vote나 bookmark를 시도할 수 있도록 구조를 개선할 수도 있습니다.

필요하다면 후속 로직 개선을 위한 추가 코드를 제안해 드릴 수 있습니다.

src/hooks/common/useIsMobile.ts (1)

14-16: 이벤트 리스너 최적화가 필요합니다.

리사이즈 이벤트가 너무 자주 발생하는 것을 방지하기 위해 디바운스 처리가 필요합니다.

다음과 같이 개선해보세요:

import { debounce } from 'lodash';

function useIsMobile() {
  const [isMobile, setIsMobile] = useState<boolean>(false);

  useEffect(() => {
    const checkIsMobile = () => {
      // ... existing code ...
    };

    const debouncedCheck = debounce(checkIsMobile, 250);
    
    checkIsMobile();
    window.addEventListener('resize', debouncedCheck);
    return () => {
      window.removeEventListener('resize', debouncedCheck);
      debouncedCheck.cancel();
    };
  }, []);

  return isMobile;
}
src/components/mobile/organisms/BalanceGameEndingSection/BalanceGameEndingSection.style.ts (1)

41-47: 토스트 모달의 접근성 개선 필요

현재 토스트 모달 구현에 다음 접근성 속성들이 누락되어 있습니다:

  • ARIA 역할 및 라이브 리전
  • 스크린 리더 지원

다음과 같은 개선을 제안드립니다:

export const toastModalStyling = css({
  position: 'fixed',
  top: '65px',
  left: '50%',
  transform: 'translate(-50%)',
  zIndex: '1000',
+  role: 'alert',
+  'aria-live': 'polite',
});
src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1)

36-41: 이미지 최적화 속성 추가 필요

이미지 스타일에 성능 및 사용자 경험 향상을 위한 속성들이 누락되어 있습니다.

다음과 같은 개선을 제안드립니다:

export const imageStyle = css({
  width: '90px',
  height: '90px',
  borderRadius: '10px',
  objectFit: 'cover',
+  loading: 'lazy',
+  decoding: 'async',
});
src/layout/layout.tsx (2)

17-19: 미디어 쿼리 브레이크포인트 상수화 필요

하드코딩된 미디어 쿼리 브레이크포인트는 일관성 유지에 문제가 될 수 있습니다.

상수 파일을 생성하여 브레이크포인트를 관리하는 것을 추천드립니다:

// src/constants/breakpoints.ts
export const BREAKPOINTS = {
  MOBILE: '430px',
  TABLET: '768px',
  DESKTOP: '1024px',
} as const;

그리고 다음과 같이 사용:

-          '@media (max-width: 430px)': {
+          [`@media (max-width: ${BREAKPOINTS.MOBILE})`]: {
            paddingTop: '55px',
          },

24-24: Footer 조건부 렌더링 최적화 필요

삼항 연산자를 사용한 조건부 렌더링은 가독성이 떨어지며, 불필요한 null 반환이 있습니다.

다음과 같은 개선을 제안드립니다:

-      {isMobile ? null : <Footer />}
+      {!isMobile && <Footer />}
src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx (3)

1-1: ESLint 비활성화 주석에 대한 설명이 필요합니다.

ESLint 규칙을 비활성화한 이유를 주석으로 설명해주시면 좋겠습니다.


6-14: 타입 정의 개선이 필요합니다.

다음 사항들을 개선하면 좋겠습니다:

  1. id prop이 선택적인 이유가 명확하지 않습니다.
  2. 'A' | 'B' 타입이 반복되므로 별도의 타입으로 분리하는 것이 좋겠습니다.
+type OptionType = 'A' | 'B';
+
 export interface BalanceGameButtonProps {
-  id?: number;
+  id: number;
   name: string;
   imgUrl: string | null;
   description: string;
-  optionType: 'A' | 'B';
-  selectedButton: 'A' | 'B' | null;
-  onClick: (optionType: 'A' | 'B') => void;
+  optionType: OptionType;
+  selectedButton: OptionType | null;
+  onClick: (optionType: OptionType) => void;
 }

31-33: 조건부 렌더링 로직 개선이 필요합니다.

renderCheckedIcon 함수를 더 간단하게 작성할 수 있습니다.

-  const renderCheckedIcon = () => {
-    return optionType === 'A' ? <RedCheckIcon /> : <BlueCheckIcon />;
-  };
+  const renderCheckedIcon = () => (
+    optionType === 'A' ? <RedCheckIcon /> : <BlueCheckIcon />
+  );
src/stories/mobile/atoms/BalanceGameButton.stories.tsx (2)

1-1: eslint-disable 주석 제거 필요

프로덕션 코드에서 alert 사용을 피하고 더 적절한 피드백 메커니즘을 구현하는 것이 좋습니다.


38-43: 테스트 데이터 개선 필요

현재 사용 중인 샘플 데이터가 실제 사용 사례를 잘 반영하지 못하고 있습니다. 다음과 같은 개선을 제안합니다:

  • 더 현실적인 제목과 설명 텍스트 사용
  • 다양한 길이의 텍스트 케이스 추가
src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.tsx (1)

24-30: 상태 업데이트 로직 최적화 필요

handleNextGamehandlePrevGame 함수를 하나의 함수로 통합하여 코드 중복을 줄일 수 있습니다.

- const handleNextGame = () => {
-   setCurrentStage((stage) => (stage < 10 ? stage + 1 : stage));
- };
- 
- const handlePrevGame = () => {
-   setCurrentStage((stage) => (stage > 0 ? stage - 1 : stage));
- };
+ const handleStageChange = (direction: 'next' | 'prev') => {
+   setCurrentStage((stage) => {
+     if (direction === 'next') return stage < 10 ? stage + 1 : stage;
+     return stage > 0 ? stage - 1 : stage;
+   });
+ };
src/components/mobile/molecules/BalanceGameBox/BalanceGameBox.tsx (1)

53-62: 옵션 A의 널 체크 개선 필요

옵션 데이터의 안전한 처리를 위해 옵셔널 체이닝 사용이 중복되어 있습니다.

다음과 같이 구조 분해 할당과 기본값을 활용하여 코드를 개선할 수 있습니다:

- <BalanceGameButton
-   name={optionA?.name ?? ''}
-   imgUrl={optionA?.imgUrl ?? null}
-   description={optionA?.description ?? ''}
-   optionType={optionA?.optionType ?? 'A'}
-   selectedButton={selectedVote ?? null}
-   onClick={() => {
-     handleButtonClick('A');
-   }}
- />
+ <BalanceGameButton
+   name={optionA?.name || ''}
+   imgUrl={optionA?.imgUrl || null}
+   description={optionA?.description || ''}
+   optionType="A"
+   selectedButton={selectedVote || null}
+   onClick={() => handleButtonClick('A')}
+ />
src/components/molecules/BalanceGameBox/BalanceGameBox.tsx (1)

Line range hint 34-39: 배경 이미지 초기화 로직 최적화 필요

getRandomImages 함수가 컴포넌트 렌더링마다 새로 생성되어 불필요한 메모리를 사용할 수 있습니다.

다음과 같이 useMemo를 사용하여 최적화하는 것을 권장드립니다:

- const getRandomImages = () => {
+ const getRandomImages = useMemo(() => {
    const [randomNumberA, randomNumberB] = getRandomNumbers(
      S.gameBackgrounds.length,
    );
    return [S.gameBackgrounds[randomNumberA], S.gameBackgrounds[randomNumberB]];
- };
+ }, []);
src/stories/mobile/molecules/BalanceGameBox.stories.tsx (2)

11-28: 테스트 데이터 관리 개선 필요

테스트용 예제 데이터가 하드코딩되어 있어 유지보수가 어려울 수 있습니다.

별도의 테스트 데이터 파일로 분리하여 관리하는 것을 권장드립니다:

// src/stories/fixtures/balanceGame.ts
export const mockGameOptions: GameOption[] = [
  {
    id: 0,
    name: '제목은글자제한30자제목은글자제한30자제목은글자제한30자',
    imgUrl: SampleWhole,
    description: '상황설명글자제한50상황설명글자제한50상황설명글자제한50상황설명글자제한50상황설명글자제한50',
    optionType: 'A',
  },
  // ... 옵션 B
];

76-93: 스토리 렌더링 구조 개선 필요

현재 스토리 렌더링 구조가 반복적이며, 재사용성이 낮습니다.

다음과 같이 맵 함수를 사용하여 코드를 개선할 수 있습니다:

export const All: Story = {
  render: (args) => (
    <ul css={storyContainer}>
-     <li css={storyInnerContainer}>
-       <h3>not selected</h3>
-       <BalanceGameBox {...args} />
-     </li>
-     <li css={storyInnerContainer}>
-       <h3>A selected</h3>
-       <BalanceGameBox {...args} selectedVote="A" />
-     </li>
-     <li css={storyInnerContainer}>
-       <h3>B selected</h3>
-       <BalanceGameBox {...args} selectedVote="B" />
-     </li>
+     {[
+       { title: 'not selected', vote: null },
+       { title: 'A selected', vote: 'A' },
+       { title: 'B selected', vote: 'B' },
+     ].map(({ title, vote }) => (
+       <li key={title} css={storyInnerContainer}>
+         <h3>{title}</h3>
+         <BalanceGameBox {...args} selectedVote={vote} />
+       </li>
+     ))}
    </ul>
  ),
};
src/stories/mobile/organisms/BalanceGameSection.stories.tsx (1)

68-71: 핸들러 함수의 기본 구현 개선 필요

빈 함수 대신 콘솔 로그나 알림을 추가하여 Storybook에서 상호작용을 더 명확하게 표시하는 것이 좋습니다.

-    setCurrentStage: () => {},
-    handleNextGame: () => {},
-    handlePrevGame: () => {},
+    setCurrentStage: () => console.log('현재 스테이지 변경'),
+    handleNextGame: () => console.log('다음 게임으로 이동'),
+    handlePrevGame: () => console.log('이전 게임으로 이동'),
src/components/mobile/organisms/BalanceGameEndingSection/BalanceGameEndingSection.tsx (1)

32-33: 게스트 사용자 확인 로직 분리 제안

게스트 사용자 확인 로직을 커스텀 훅으로 분리하면 재사용성이 향상되고 코드가 더 깔끔해질 것 같습니다.

-  const isGuest = !localStorage.getItem('accessToken');
+  const { isGuest } = useAuthStatus();
src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.style.ts (1)

99-101: 그라데이션 성능 최적화 고려

선형 그라데이션은 모바일 기기에서 성능에 영향을 줄 수 있습니다.
가능하다면 더 가벼운 대안을 고려하거나, will-change 속성을 사용하여 최적화하세요.

   background:
     'linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))',
+  willChange: 'transform',
src/App.tsx (1)

24-24: 모바일 컴포넌트 import 구문 검토

모바일 페이지 컴포넌트를 동적 import로 변경하여 초기 번들 크기를 최적화하는 것이 좋습니다.

다음과 같이 React.lazy를 사용하여 코드 분할을 구현하는 것을 추천드립니다:

-import BalanceGameMobilePage from './pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage';
+const BalanceGameMobilePage = React.lazy(() => 
+  import('./pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage')
+);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 955bc16 and c55cffb.

⛔ Files ignored due to path filters (2)
  • src/assets/svg/blue-check-icon.svg is excluded by !**/*.svg
  • src/assets/svg/red-check-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (27)
  • public/index.html (1 hunks)
  • src/App.tsx (3 hunks)
  • src/assets/index.ts (1 hunks)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1 hunks)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx (1 hunks)
  • src/components/mobile/atoms/Button/Button.style.ts (1 hunks)
  • src/components/mobile/atoms/IconButton/IconButton.tsx (1 hunks)
  • src/components/mobile/atoms/InteractionButton/InteractionButton.tsx (1 hunks)
  • src/components/mobile/molecules/BalanceGameBox/BalanceGameBox.style.ts (1 hunks)
  • src/components/mobile/molecules/BalanceGameBox/BalanceGameBox.tsx (1 hunks)
  • src/components/mobile/organisms/BalanceGameEndingSection/BalanceGameEndingSection.style.ts (1 hunks)
  • src/components/mobile/organisms/BalanceGameEndingSection/BalanceGameEndingSection.tsx (1 hunks)
  • src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.style.ts (1 hunks)
  • src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (1 hunks)
  • src/components/molecules/BalanceGameBox/BalanceGameBox.tsx (2 hunks)
  • src/components/molecules/BalanceGameEndingBox/BalanceGameEndingBox.tsx (3 hunks)
  • src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (5 hunks)
  • src/hooks/common/useIsMobile.ts (1 hunks)
  • src/hooks/game/useBalanceGameBookmark.ts (1 hunks)
  • src/hooks/game/useBalanceGameVote.ts (1 hunks)
  • src/layout/layout.tsx (1 hunks)
  • src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.style.ts (1 hunks)
  • src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.tsx (1 hunks)
  • src/stories/mobile/atoms/BalanceGameButton.stories.tsx (1 hunks)
  • src/stories/mobile/molecules/BalanceGameBox.stories.tsx (1 hunks)
  • src/stories/mobile/organisms/BalanceGameEndingSection.stories.tsx (1 hunks)
  • src/stories/mobile/organisms/BalanceGameSection.stories.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • public/index.html
  • src/components/mobile/molecules/BalanceGameBox/BalanceGameBox.style.ts
  • src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.style.ts
🔇 Additional comments (14)
src/hooks/game/useBalanceGameVote.ts (1)

1-7: 임포트 구문 확인
필요한 모듈들을 깔끔하게 임포트하고 있습니다. 중복 임포트나 사용되지 않는 임포트가 없어 보이며, 기능별로 잘 분리되어 있어 가독성이 좋습니다.

src/components/molecules/BalanceGameEndingBox/BalanceGameEndingBox.tsx (2)

4-4: 불필요 임포트 제거 확인
Bookmark 관련 로직이 별도 훅으로 이동하며 ERROR 임포트가 삭제된 것으로 보입니다. 임포트 누락이나 사용되지 않는 부분이 없는지 최종적으로 확인하시면 좋겠습니다.


102-106: 컴포넌트의 onClick 핸들러
onClick에서 바로 훅 함수를 호출하고 있는데, 페이지 이동 또는 로그인 모달이 뜨기 전에 의도치 않은 사이드이펙트가 생기지 않는지 점검해 주세요. 필요하다면 useCallback, useMemo 등을 통해 함수 참조를 최적화할 수 있습니다.

src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (2)

1-19: 기본 구조와 임포트 검토
새로운 모바일 섹션 컴포넌트로, 필요한 컴포넌트와 훅들을 깔끔하게 임포트하고 있습니다. 중복되거나 사용하지 않는 모듈이 없어 보이나, IconButton, ToastModal 등 핵심 컴포넌트가 적절히 동작하는지 UI 테스트를 꼭 병행해주세요.


85-92: handleNextButton 로직 점검
게스트 여부와 투표 여부를 조건으로 다음 단계 이동을 제한하는 로직이 정상적으로 작동해 보입니다. 다만, 사용자 입장에서 '투표 안 했을 경우' 어떤 UI/UX가 노출되는지(예: 토스트 안내, 비활성화 등) 명확히 처리되는지 확인 부탁드립니다.

src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (2)

4-4: VoteRecord 타입 추가
투표 정보를 명확히 관리하기 위해 VoteRecord를 임포트해 사용하는 것은 바람직한 설계입니다. 특히 게스트/회원 구분 로직에서 직관적인 타입 추론이 가능해집니다.


63-69: handleGuestGameVote 호출
currentGame을 기반으로 guestVotedList를 업데이트하는 흐름이 직관적입니다. gameDetailResponses가 변경・추가될 경우 로직상의 경계값 오류(off-by-one 등)에 유의해 주세요.

src/components/mobile/atoms/IconButton/IconButton.tsx (1)

1-1: ref 전달 구현이 개선되었습니다!

ComponentPropsWithRef를 사용하여 ref 전달을 올바르게 처리하도록 개선되었습니다. 이는 React의 모범 사례를 잘 따르고 있습니다.

Also applies to: 4-4, 9-9

src/components/mobile/atoms/InteractionButton/InteractionButton.tsx (1)

1-1: 컴포넌트 구조가 잘 개선되었습니다!

IconButton과 일관된 패턴을 따르며, ref 처리가 올바르게 구현되었습니다. 프로퍼티 타입 정의와 스타일 분리도 잘 되어있습니다.

Also applies to: 4-4

src/stories/mobile/organisms/BalanceGameEndingSection.stories.tsx (2)

17-22: 기본 props 구성이 적절해 보입니다.

스토리북 args 설정이 명확하고 테스트에 필요한 모든 필수 props가 포함되어 있습니다.


23-33: 데코레이터 설정이 잘 구성되어 있습니다.

Provider들의 중첩 순서가 적절하며, 컴포넌트 테스트에 필요한 모든 컨텍스트가 제공되고 있습니다.

src/components/mobile/atoms/Button/Button.style.ts (1)

48-48: 버튼 너비 변경에 대한 검증이 필요합니다.

primary 버튼의 medium 사이즈 너비가 130px에서 160px로 변경되었습니다. 이 변경이 기존 모바일 레이아웃에 미치는 영향을 확인해주세요.

✅ Verification successful

버튼 너비 변경은 안전합니다

primary variant의 medium 사이즈 버튼은 스토리북 예제에서만 사용되고 있으며, 실제 프로덕션 코드에서는 해당 조합으로 사용되는 곳이 없습니다. GameTagModal과 ShareModal 컴포넌트에서 Button을 임포트하고 있지만 primary variant와 medium 사이즈 조합으로는 사용하지 않습니다.

따라서 버튼 너비를 130px에서 160px로 변경하는 것은 기존 레이아웃에 영향을 주지 않을 것으로 판단됩니다.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# primary variant medium 버튼이 사용된 컴포넌트들을 검색합니다
rg -l "variant=\"?primary\"?.+size=\"?medium\"?" --type tsx

Length of output: 85


Script:

#!/bin/bash
# Search for Button component usage with primary variant and medium size
rg -l "variant.*primary.*size.*medium|size.*medium.*variant.*primary"

# Also check for any direct imports of the Button component
rg -l "import.*Button.*from.*components/mobile/atoms/Button"

Length of output: 342


Script:

#!/bin/bash
# Check the actual usage context in the files that import the Button
rg "Button.*variant.*primary|variant.*primary.*Button" -A 3 -B 3 src/components/mobile/molecules/GameTagModal/GameTagModal.tsx src/components/mobile/molecules/ShareModal/ShareModal.tsx

# Check the story file to see how the button is used in different scenarios
cat src/stories/mobile/atoms/Button.stories.tsx

Length of output: 2200

src/stories/mobile/organisms/BalanceGameSection.stories.tsx (1)

14-19: 예시 데이터의 텍스트 길이 제한 검증 필요

예시 데이터의 텍스트가 실제 제한 길이와 일치하는지 확인이 필요합니다:

  • 제목: 정확히 30자
  • 설명: 정확히 50자
    실제 사용자 입력이 이 제한을 초과할 경우의 처리도 테스트해야 합니다.
src/App.tsx (1)

51-51: useIsMobile 훅 사용 검토

모바일 감지 로직이 올바르게 구현되었는지 확인이 필요합니다.

✅ Verification successful

모바일 감지 로직이 적절하게 구현되었습니다

useIsMobile 훅의 구현을 확인한 결과:

  • 화면 너비가 430px 이하인 경우를 모바일로 감지
  • 터치 이벤트를 지원하고 화면 너비가 430px 이하인 경우도 모바일로 감지
  • window.matchMedia와 ontouchstart를 사용한 표준적인 모바일 감지 방식 사용
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# useIsMobile 훅의 구현 확인
rg -A 10 "useIsMobile" src/hooks/common/useIsMobile.ts

Length of output: 416

src/hooks/game/useBalanceGameVote.ts Show resolved Hide resolved
Comment on lines +15 to +36
useEffect(() => {
const updateGuestVotedList = () => {
const storedVotes = localStorage.getItem(`game_${gameSetId}`);
setGuestVotedList(
storedVotes ? (JSON.parse(storedVotes) as VoteRecord[]) : [],
);
};

updateGuestVotedList();

const handleStorageChange = (event: StorageEvent) => {
if (event.key === `game_${gameSetId}`) {
updateGuestVotedList();
}
};

window.addEventListener('storage', handleStorageChange);
updateGuestVotedList();
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [gameSetId, setGuestVotedList]);
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

로컬 스토리지 데이터 파싱 예외처리 필요

VoteRecord 타입 정의를 확인한 결과, localStorage에서 파싱된 데이터가 { gameId: number; votedOption: 'A' | 'B' }[] 형태의 배열이어야 합니다. 현재 코드에서는 JSON.parse 실패 시 예외처리가 없어 잘못된 형식의 데이터가 있을 경우 런타임 에러가 발생할 수 있습니다.

다음과 같이 수정이 필요합니다:

  • updateGuestVotedList 함수 내에서 try-catch 블록으로 JSON.parse를 감싸서 파싱 실패 시 빈 배열을 반환하도록 처리
  • 파싱된 데이터가 VoteRecord[] 타입과 일치하는지 검증하는 로직 추가
🔗 Analysis chain

로컬 스토리지 동기화 로직 검토
useEffect에서 로컬 스토리지 변경 감지를 통해 guest voting 리스트를 동기화하는 방식이 적절합니다. 다만, localStorage에 문자열로 저장된 데이터를 JSON.parse 할 때 예외처리(잘못된 형식 등)에 대한 처리가 필요할 수 있습니다.

로컬 스토리지에서 가져온 데이터가 JSON.parse 중에 에러가 나는 경우를 확인하기 위한 스크립트를 실행해 보세요:


Let me search for more context about how this data is being written to localStorage and the VoteRecord type definition.


Let me check the interface or type definition for VoteRecord since it wasn't found in the previous search.


Let me check the VoteRecord type definition from the types directory.


Let me try a different approach to find the vote types file.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 프로젝트 전역에서 game_<숫자> 형태로 localStorage 접근하는 코드가 있는지 검사
rg -A 5 "localStorage.getItem\(.*game_"

Length of output: 449


Script:

#!/bin/bash
# Search for localStorage.setItem with game_ prefix
rg "localStorage.setItem\(.*game_" -A 5

# Find VoteRecord type definition
ast-grep --pattern 'type VoteRecord = $_'

Length of output: 434


Script:

#!/bin/bash
# Search for VoteRecord interface definition
ast-grep --pattern 'interface VoteRecord $_'

# Get the full content of useBalanceGameVote.ts to see the type imports
rg -g "useBalanceGameVote.ts" "VoteRecord" -B 5 -A 5

Length of output: 1904


Script:

#!/bin/bash
# Find and show the content of the vote types file
fd "vote.ts" "types" --exec cat {}

Length of output: 127


Script:

#!/bin/bash
# Find the vote types file in src directory
fd "vote.ts" "src" --exec cat {}

# Also search for any JSON.parse error handling in the codebase
rg "try.*JSON\.parse" -A 5

Length of output: 4989

src/hooks/common/useIsMobile.ts Show resolved Hide resolved
src/App.tsx Show resolved Hide resolved
src/assets/index.ts Show resolved Hide resolved
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/stories/organisms/SideBar.stories.tsx (1)

16-22: 데코레이터 중복 코드 개선 제안

ActionBox.stories.tsx와 동일한 데코레이터 코드가 중복되어 있습니다. 공통 데코레이터를 별도의 파일로 분리하여 재사용하는 것을 제안드립니다.

다음과 같이 구현할 수 있습니다:

// src/stories/decorators/withRouter.tsx
+import { BrowserRouter } from 'react-router-dom';
+
+export const withRouter = (Story) => (
+  <BrowserRouter>
+    <Story />
+  </BrowserRouter>
+);

// src/stories/organisms/SideBar.stories.tsx
-decorators: [
-  (Story) => (
-    <BrowserRouter>
-      <Story />
-    </BrowserRouter>
-  ),
-],
+decorators: [withRouter],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c55cffb and d026d07.

📒 Files selected for processing (4)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1 hunks)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx (1 hunks)
  • src/stories/molecules/ActionBox.stories.tsx (2 hunks)
  • src/stories/organisms/SideBar.stories.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts
🔇 Additional comments (2)
src/stories/molecules/ActionBox.stories.tsx (1)

3-3: 라우팅 컨텍스트가 올바르게 추가되었습니다!

스토리북 데코레이터를 통해 BrowserRouter를 추가한 것은 적절한 구현입니다. 이를 통해 라우팅 기능이 필요한 컴포넌트들을 스토리북에서 정상적으로 테스트할 수 있게 되었습니다.

Also applies to: 14-20

src/stories/organisms/SideBar.stories.tsx (1)

3-3: 라우팅 컨텍스트가 올바르게 추가되었습니다!

스토리북 데코레이터를 통해 BrowserRouter를 추가한 것은 적절한 구현입니다.

Also applies to: 16-22

Copy link
Collaborator

@alwubin alwubin left a comment

Choose a reason for hiding this comment

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

밸런스게임 조회 모바일 페이지 구현하시느라 수고 많으셨습니다~!! 컴포넌트들이 구조적으로 명확하게 분리되어 있어서 감탄했습니당👏 코멘트 확인 부탁드립니당

) => {
const updatedVotes = [...guestVotedList];
const currentVoteIndex = updatedVotes.findIndex(
(vote) => vote.gameId === game?.gameDetailResponses[currentStage]?.id,
Copy link
Collaborator

Choose a reason for hiding this comment

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

game?.gameDetailResponses[currentStage]?.id 반복적으로 사용되고 있는데 별도의 변수로 처리 후 사용하는건 어떨까용?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ac0003e 수정했습니다!!

Comment on lines 24 to 30
const handleNextGame = () => {
setCurrentStage((stage) => (stage < 10 ? stage + 1 : stage));
};

const handlePrevGame = () => {
setCurrentStage((stage) => (stage > 0 ? stage - 1 : stage));
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

비슷한 로직이 반복되고 있는데

const changeStage = (step: number) => {
  setCurrentStage((stage) => Math.min(10, Math.max(0, stage + step)));
};

로 공통 함수로 분리하는건 어떨까용

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

3bdad95 수정했습니다!! props가 더 간결해졌네용 👏

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
14.8% Duplication on New Code (required ≤ 10%)

See analysis details on SonarQube Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1)

5-14: 🛠️ Refactor suggestion

버튼의 접근성 및 반응형 디자인 개선 필요

버튼 컴포넌트의 기본 접근성이 누락되어 있으며, 고정된 너비 값은 다양한 화면 크기에서 문제를 일으킬 수 있습니다.

export const buttonWrapStyle = css({
  all: 'unset',
  cursor: 'pointer',
  display: 'flex',
  flexDirection: 'column',
-  width: '305px',
+  width: '100%',
+  maxWidth: '305px',
  backgroundColor: color.WT,
  borderRadius: '10px',
  overflow: 'hidden',
+  role: 'button',
+  'aria-pressed': 'false',
});
🧹 Nitpick comments (4)
src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (1)

9-9: 불필요한 중복 슬래시를 제거해 주세요.

import 구문에 중복된 슬래시가 있습니다. 경로 가독성을 위해 아래와 같이 변경하는 것을 권장합니다.

- import Button from '@/components/mobile//atoms/Button/Button';
+ import Button from '@/components/mobile/atoms/Button/Button';
src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (3)

16-23: 타입 안정성 개선 제안

옵션 타입을 리터럴 유니온 타입으로 분리하여 타입 안정성을 향상시킬 수 있습니다.

+type OptionType = 'A' | 'B';

-export const getOutlineStyle = (option: 'A' | 'B', isSelected: boolean) => {
+export const getOutlineStyle = (option: OptionType, isSelected: boolean) => {

33-36: 타이포그래피 시스템 일관성 유지 필요

타이포그래피 시스템을 재정의하는 것은 일관성을 해칠 수 있습니다. 기존 타이포그래피 시스템을 활용하거나, 필요한 경우 시스템을 확장하는 것이 좋습니다.

-export const nameStyle = css(typo.Mobile.Text.SemiBold_14, {
-  width: '220px',
-  fontSize: '16px',
-});
+export const nameStyle = css(typo.Mobile.Text.SemiBold_16, {
+  width: '220px',
+});

38-43: 이미지 처리 방식 개선 제안

고정된 이미지 크기는 이미지 품질에 영향을 줄 수 있습니다. 반응형 크기와 이미지 최적화를 고려해보세요.

이미지 컴포넌트에서 다음 사항들을 고려해주세요:

  • 이미지 최적화 (next/image 사용)
  • 반응형 크기 처리
  • 이미지 로딩 상태 처리
  • alt 텍스트 지원
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d026d07 and ac0003e.

📒 Files selected for processing (7)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1 hunks)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx (1 hunks)
  • src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (1 hunks)
  • src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (8 hunks)
  • src/hooks/game/useBalanceGameVote.ts (1 hunks)
  • src/pages/BalanceGamePage/BalanceGamePage.tsx (2 hunks)
  • src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.tsx
  • src/pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage.tsx
🔇 Additional comments (11)
src/components/mobile/organisms/BalanceGameSection/BalanceGameSection.tsx (3)

70-81: 내려받은 데이터 변경 시 초기 북마크 스테이지 이동 로직 점검 필요.

useEffect에서 한 번만 동작하도록 설정하고 있으나, game 데이터가 새로 로드되거나 갱신될 때 재실행이 필요한 경우가 있을 수 있습니다. 필요하다면 의존성 배열을 조정해 보세요.


83-90: 다음 단계 이동 로직이 명확합니다.

비로그인 상태와 로그인 상태를 모두 체크한 후에만 넘어가도록 설계된 부분이 좋습니다.


92-100: 에러 핸들링 로직 재확인 필요 (북마크 훅).

북마크 처리 훅에서 에러가 발생할 경우를 대비해 별도의 에러 처리 로직을 도입하거나, 사용자에게 알림을 줄 방법을 고려해 보세요.

src/pages/BalanceGamePage/BalanceGamePage.tsx (1)

25-26: 단일 함수로 단계 전환을 처리하는 방식이 깔끔합니다.

이전과 다음 단계를 일원화하여 처리함으로써 코드 복잡도가 낮아졌습니다. 이 로직으로 인해 stage 범위를 벗어나지 않도록 검증하는 부분도 잘 구현되었습니다.

src/components/mobile/atoms/BalanceGameButton/BalanceGameButton.style.ts (1)

45-56: 구조가 잘 잡혀있습니다!

콘텐츠 래퍼와 설명 스타일이 잘 구성되어 있습니다. 모바일 타이포그래피를 적절히 확장하여 사용하고 있습니다.

src/hooks/game/useBalanceGameVote.ts (3)

67-81: 🛠️ Refactor suggestion

뮤테이션 상태 관리 개선 필요

현재 구현에서 다음과 같은 개선이 필요합니다:

  1. 뮤테이션의 로딩 상태 관리
  2. 에러 처리
  3. 동시 호출 방지

다음과 같이 개선하는 것을 추천드립니다:

 export const useUserGameVote = (
   gameSetId: number,
   gameId: number,
   handleNextStage: () => void,
 ) => {
-  const { mutate: createGameVote } = useCreateGameVoteMutation(
+  const { 
+    mutate: createGameVote,
+    isLoading: isCreating 
+  } = useCreateGameVoteMutation(
     gameSetId,
     gameId,
   );
-  const { mutate: editGameVote } = useEditGameVoteMutation(gameSetId, gameId);
-  const { mutate: deleteGameVote } = useDeleteGameVoteMutation(
+  const { 
+    mutate: editGameVote,
+    isLoading: isEditing 
+  } = useEditGameVoteMutation(gameSetId, gameId);
+  const { 
+    mutate: deleteGameVote,
+    isLoading: isDeleting 
+  } = useDeleteGameVoteMutation(
     gameSetId,
     gameId,
   );
+
+  const isLoading = isCreating || isEditing || isDeleting;

Likely invalid or redundant comment.


15-36: ⚠️ Potential issue

localStorage 데이터 처리 개선 필요

localStorage에서 데이터를 파싱할 때 발생할 수 있는 예외 상황에 대한 처리가 필요합니다:

다음과 같이 개선하는 것을 추천드립니다:

 const updateGuestVotedList = () => {
   const storedVotes = localStorage.getItem(`game_${gameSetId}`);
-  setGuestVotedList(
-    storedVotes ? (JSON.parse(storedVotes) as VoteRecord[]) : [],
-  );
+  try {
+    if (!storedVotes) {
+      setGuestVotedList([]);
+      return;
+    }
+    const parsedVotes = JSON.parse(storedVotes);
+    if (Array.isArray(parsedVotes) && 
+        parsedVotes.every(vote => 
+          typeof vote.gameId === 'number' && 
+          (vote.votedOption === 'A' || vote.votedOption === 'B')
+        )) {
+      setGuestVotedList(parsedVotes);
+    } else {
+      console.error('Invalid vote data structure');
+      setGuestVotedList([]);
+    }
+  } catch (error) {
+    console.error('Failed to parse vote data:', error);
+    setGuestVotedList([]);
+  }
 };

Likely invalid or redundant comment.


82-104: 🛠️ Refactor suggestion

투표 처리의 일관성 개선 필요

현재 구현에서 다음과 같은 문제점이 있습니다:

  1. createGameVote만 onSuccess 콜백이 구현되어 있음
  2. 뮤테이션 진행 중 중복 호출 방지 로직 없음
  3. 에러 처리가 일관적이지 않음

다음과 같이 개선하는 것을 추천드립니다:

 const handleUserGameVote = (
   selectedOption: MyVoteOption,
   voteOption: VoteOption,
 ) => {
+  if (isLoading) return;
+
   if (!selectedOption) {
     createGameVote(voteOption, {
       onSuccess: () => {
         const nextStageTimer = setTimeout(() => {
           handleNextStage();
         }, 500);
         return () => clearTimeout(nextStageTimer);
       },
+      onError: (error) => {
+        console.error('Failed to create vote:', error);
+      },
     });
   } else if (selectedOption === voteOption) {
-    deleteGameVote();
+    deleteGameVote(undefined, {
+      onSuccess: () => {
+        console.log('Vote deleted successfully');
+      },
+      onError: (error) => {
+        console.error('Failed to delete vote:', error);
+      },
+    });
   } else {
-    editGameVote(voteOption);
+    editGameVote(voteOption, {
+      onSuccess: () => {
+        console.log('Vote updated successfully');
+      },
+      onError: (error) => {
+        console.error('Failed to update vote:', error);
+      },
+    });
   }
 };

Likely invalid or redundant comment.

src/components/organisms/BalanceGameSection/BalanceGameSection.tsx (3)

Line range hint 23-67: 구조가 잘 정의되어 있습니다!

컴포넌트의 props 인터페이스가 명확하게 정의되어 있고, 게스트 투표 관리를 위한 커스텀 훅 사용이 적절합니다.


Line range hint 89-114: 스테이지 네비게이션 로직이 적절합니다!

타이머 정리와 투표 여부 확인이 잘 구현되어 있습니다.


117-124: 북마크 처리 로직이 잘 구현되어 있습니다!

useGameBookmark 훅을 통한 관심사 분리와 게스트 사용자 처리가 적절합니다.

Comment on lines +38 to +62
const handleGuestGameVote = (
selectedOption: MyVoteOption,
voteOption: VoteOption,
) => {
const updatedVotes = [...guestVotedList];
const gameStageId = game?.gameDetailResponses[currentStage]?.id;

const currentVoteIndex = updatedVotes.findIndex(
(vote) => vote.gameId === gameStageId,
);

if (!selectedOption) {
updatedVotes.push({
gameId: gameStageId as number,
votedOption: voteOption,
});
} else if (selectedOption === voteOption) {
updatedVotes.splice(currentVoteIndex, 1);
} else {
updatedVotes[currentVoteIndex].votedOption = voteOption;
}

setGuestVotedList(updatedVotes);
localStorage.setItem(`game_${gameSetId}`, JSON.stringify(updatedVotes));
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

게임 스테이지 ID 처리 및 코드 최적화 필요

  1. gameStageId가 undefined일 수 있는 상황에 대한 처리가 필요합니다.
  2. gameStageId를 반복적으로 참조하는 부분을 최적화할 수 있습니다.

다음과 같이 개선하는 것을 추천드립니다:

 const handleGuestGameVote = (
   selectedOption: MyVoteOption,
   voteOption: VoteOption,
 ) => {
+  const gameStageId = game?.gameDetailResponses[currentStage]?.id;
+  if (typeof gameStageId !== 'number') {
+    console.error('Invalid game stage ID');
+    return;
+  }
+
   const updatedVotes = [...guestVotedList];
-  const gameStageId = game?.gameDetailResponses[currentStage]?.id;

   const currentVoteIndex = updatedVotes.findIndex(
     (vote) => vote.gameId === gameStageId,
   );

   if (!selectedOption) {
     updatedVotes.push({
-      gameId: gameStageId as number,
+      gameId: gameStageId,
       votedOption: voteOption,
     });
   } else if (selectedOption === voteOption) {
     updatedVotes.splice(currentVoteIndex, 1);
   } else {
     updatedVotes[currentVoteIndex].votedOption = voteOption;
   }
📝 Committable suggestion

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

Suggested change
const handleGuestGameVote = (
selectedOption: MyVoteOption,
voteOption: VoteOption,
) => {
const updatedVotes = [...guestVotedList];
const gameStageId = game?.gameDetailResponses[currentStage]?.id;
const currentVoteIndex = updatedVotes.findIndex(
(vote) => vote.gameId === gameStageId,
);
if (!selectedOption) {
updatedVotes.push({
gameId: gameStageId as number,
votedOption: voteOption,
});
} else if (selectedOption === voteOption) {
updatedVotes.splice(currentVoteIndex, 1);
} else {
updatedVotes[currentVoteIndex].votedOption = voteOption;
}
setGuestVotedList(updatedVotes);
localStorage.setItem(`game_${gameSetId}`, JSON.stringify(updatedVotes));
};
const handleGuestGameVote = (
selectedOption: MyVoteOption,
voteOption: VoteOption,
) => {
const gameStageId = game?.gameDetailResponses[currentStage]?.id;
if (typeof gameStageId !== 'number') {
console.error('Invalid game stage ID');
return;
}
const updatedVotes = [...guestVotedList];
const currentVoteIndex = updatedVotes.findIndex(
(vote) => vote.gameId === gameStageId,
);
if (!selectedOption) {
updatedVotes.push({
gameId: gameStageId,
votedOption: voteOption,
});
} else if (selectedOption === voteOption) {
updatedVotes.splice(currentVoteIndex, 1);
} else {
updatedVotes[currentVoteIndex].votedOption = voteOption;
}
setGuestVotedList(updatedVotes);
localStorage.setItem(`game_${gameSetId}`, JSON.stringify(updatedVotes));
};

flexDirection: 'column',
alignItems: 'center',
width: '335px',
height: `calc(100vh - 80px)`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

vh에서 80px을 줄이는건 모바일 컴포넌트인 점을 감안하면 나쁘지 않은 선택이네요..!
다만, 이 부분은 최근에 저희가 자주 만나는 사파리나 크롬에서 정상 작동하는지 검증이 필요한 것 같습니닷

Copy link
Collaborator

Choose a reason for hiding this comment

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

+) 정상 작동이라 함은 깨짐이나 랜더링 문제를 말씀드린거에요!

title: string;
gameSetId: number;
isMyGame: boolean;
isMyEndBookmark: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 props의 네이밍은 다소 어색하게 느껴질 수도 있는 거 같습니다!

Comment on lines +17 to +20
const { mutate: createBookmark } = useCreateGameBookmarkMutation(
gameSetId,
gameId,
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

api 호출 관련해서는 error 처리 정도는 있어야한다고 생각합니다! 물론, 현재 상태에서 추후 추가하는 식으로 진행하셔도 좋아보이네요 ㅎㅎ

},
decorators: [
(Story) => (
<Provider store={store}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

이번에 추가된 스토리 파일들에는 다 redux store을 사용하던데 accessToken 관련 처리 때문에 생성한게 맞는지 질문드려요!!

const gameSetId = Number(setId);

const { gameSet } = useGameBySetId(gameSetId);
const [currentStage, setCurrentStage] = useState<number>(0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

요런 부분까지는 number을 명시하지 않아도 되지 않을까 의견 제시해봅니다!!

@@ -251,7 +185,7 @@ const BalanceGameSection = ({
? guestVotedList[currentStage]?.votedOption
: currentGame.votedOption
}
handleNextStage={handleNextStage}
handleNextStage={() => changeStage(1)}
Copy link
Collaborator

Choose a reason for hiding this comment

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

함수 네이밍이나 가독성 면에서는 이전의 방식이 더 직관적이라고 생각하는데, 전 기존의 방식도 좋다고 생각합니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✅feature 👩🏻‍💻 frontend 프론트엔드 작업 🎨 markup ✔︎pull requests pull requests 코드 체크 요청
Projects
None yet
Development

Successfully merging this pull request may close these issues.

밸런스게임 조회 모바일 페이지 ui 구현 및 api 연동
3 participants