Skip to content

Conversation

@jaeml06
Copy link
Contributor

@jaeml06 jaeml06 commented Oct 7, 2025

🚩 연관 이슈

closed #381

📝 작업 내용

승패 투표 기능 추가

image

승패 투표를 위한 페이지를 구현했습니다.

DebateVotePage

승패 투표 QR과 투표자 명단을 확인할 수 있는 페이지입니다.
image

DebateVoteResultPage

승패 투표 결과와 세부 투표결과를 확인할 수 있는 페이지입니다.
image
image

  • VoteDetailResult
    세부 결과를 확인할 수 있는 모달내용입니다
  • VoteResultRow
    투표 득표수를 애니메이션을 활용해서 표시하는 프로그레스 바입니다.
  • CountUp
    득표수를 표시할 때, 증가하는 애니메이션을 표시하는 컴포넌트입니다.

VoteParticipationPage

투표 참여자가 투표를 할 수 있는 페이지입니다.
image
image

VoteParticipationPage

투표 참여 완료 표시 페이지 입니다.
image

🗣️ 리뷰 요구사항 (선택)

local환경에서는 qr코드를 찍어서 확인할 수 없습니다. url에 pollId가 포함되어 있으니 http://localhost:3000/vote/{pollId} 형식으로 직접 들어가서 확인하실 수 있습니다.
그리고 페이지이름과 라우터 url이 적절한지 한번 확인 부탁드립니다.

Summary by CodeRabbit

  • New Features
    • 토론 승패투표 전 기능 추가: 종료 화면에서 투표 시작, 참여용 링크/QR 제공, 실시간 참가자 목록, 투표 참여(이름·팀 선택/중복 제출 방지/완료 화면), 결과 확인(우승 팀 카드·상세 결과 모달·애니메이션 카운터). 관련 라우트 4종 추가.
  • Style
    • 푸터 콘텐츠 가로 정렬을 중앙으로 조정.
    • 브랜드 배경 그래디언트 유틸리티 추가.
  • Documentation
    • 투표 관련 페이지들의 스토리북 스토리 추가.
  • Chores
    • 투표 기능용 모킹 API 핸들러 통합.

@coderabbitai
Copy link

coderabbitai bot commented Oct 7, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

승패 투표 기능을 추가. 폴 API/타입/모킹 추가, 투표 관련 훅(조회/생성/종료/제출) 신설, 라우트 4개 추가. 투표 진행/참여/완료/결과 페이지와 관련 UI 컴포넌트(카운터/바/카드/체크박스) 도입. 일부 스타일 및 푸터 정렬 보정.

Changes

Cohort / File(s) Summary
API 엔드포인트/타입 추가
src/apis/apis/poll.ts, src/apis/endpoints.ts, src/apis/responses/poll.ts
폴 생성/조회/종료/투표 조회·제출 API 함수 5종 추가, ApiUrl.poll 등록, 응답 타입 정의 신설.
뮤테이션 훅
src/hooks/mutations/useCreatePoll.ts, src/hooks/mutations/usePostVoterPollInfo.ts, src/hooks/mutations/useFetchEndPoll.ts
중복 방지 래퍼 기반의 폴 생성, 투표 제출, 폴 종료 훅 추가. 성공 시 콜백 전달.
쿼리 훅
src/hooks/query/useGetPollInfo.ts, src/hooks/query/useGetVoterPollInfo.ts
폴 정보/투표자 정보 조회 훅 추가. 옵션으로 enabled/refetchInterval 지원.
페이지 - 투표 흐름
src/page/DebateVotePage/DebateVotePage.tsx, src/page/DebateVoteResultPage/DebateVoteResultPage.tsx, src/page/VoteParticipationPage/VoteParticipationPage.tsx, src/page/VoteCompletePage/VoteCompletePage.tsx
투표 시작(QR), 결과, 참여, 완료 페이지 신설. 유효성/에러/로딩 처리, 내비게이션 및 모달 연동.
페이지 - 종료 화면 개편
src/page/DebateEndPage/DebateEndPage.tsx, src/page/DebateEndPage/DebateEndPage.stories.tsx
종료 페이지에서 메뉴 카드 UI로 교체, 폴 생성 뮤테이션 연동.
UI 컴포넌트
src/page/DebateVoteResultPage/components/WinnerCard.tsx, .../VoteDetailResult.tsx, .../AnimatedCounter.tsx, .../VoteBar.tsx, src/page/VoteParticipationPage/components/VoteTeamButton.tsx, src/components/icons/CheckBox.tsx, src/page/DebateEndPage/components/MenuCard.tsx
결과/참여/종료 화면용 시각 컴포넌트 추가(카드, 바, 카운터, 팀 버튼, 체크박스, 메뉴 카드).
레이아웃/스타일
src/layout/components/footer/StickyFooterWrapper.tsx, tailwind.config.js
푸터 정렬 보정(justify-center), 배경 이미지 유틸 추가.
라우팅
src/routes/routes.tsx
경로 추가: /table/customize/:id/end/vote, /table/customize/:id/end/vote/result, /vote/:id, /vote/end.
타입 정의
src/type/type.ts
폴 관련 베이스/상세/투표자 타입 및 TEAM_STYLE, TeamKey 추가.
모킹
src/mocks/handlers/poll.ts, src/mocks/handlers/global.ts
폴 관련 MSW 핸들러 추가 및 글로벌 핸들러 병합.
스토리북
src/page/DebateVotePage/DebateVotePage.stories.tsx, src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx, src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx, src/page/VoteCompletePage/VoteCompletePage.stories.tsx
신규 페이지 스토리 추가 및 라우트/레이아웃 파라미터 설정.

Sequence Diagram(s)

sequenceDiagram
  actor Host as DebateEndPage(호스트)
  participant Hook as usePostPoll
  participant API as apis/poll.postPoll
  participant S as Server
  participant R as Router

  Host->>Hook: mutate(tableId)
  Hook->>API: postPoll(tableId)
  API->>S: POST /api/polls/{tableId}
  S-->>API: 201 { id, pros/cons... }
  API-->>Hook: PostPollResponseType
  Hook-->>Host: onSuccess(id)
  Host->>R: navigate(/table/.../end/vote?pollId)
Loading
sequenceDiagram
  actor Voter as VoteParticipationPage(참여자)
  participant Q as useGetPollInfo
  participant API1 as apis/poll.getPollInfo
  participant M as usePostVoterPollInfo
  participant API2 as apis/poll.postVoterPollInfo
  participant S as Server
  participant R as Router

  Voter->>Q: useGetPollInfo(pollId)
  Q->>API1: getPollInfo(pollId)
  API1->>S: GET /api/polls/{pollId}
  S-->>API1: 200 PollInfo
  API1-->>Q: data
  Voter->>M: mutate({pollId, voterInfo})
  M->>API2: postVoterPollInfo(pollId, voterInfo)
  API2->>S: POST /api/polls/{pollId}/votes
  S-->>API2: 200 VoterPollInfo
  API2-->>M: success
  M-->>Voter: onSuccess()
  Voter->>R: navigate(/vote/end)
Loading
sequenceDiagram
  actor Host as DebateVotePage(호스트)
  participant Q as useGetPollInfo (refetch 5s)
  participant E as useFetchEndPoll
  participant API1 as GET /polls/{id}
  participant API2 as PATCH /polls/{id}
  participant S as Server
  participant R as Router

  Host->>Q: start refetching
  Q->>API1: GET
  API1->>S: request
  S-->>API1: 200 PollInfo
  API1-->>Q: data (참여자 목록)
  Host->>E: mutate(pollId) "투표 결과 보기"
  E->>API2: PATCH /polls/{id}
  API2->>S: request
  S-->>API2: 200 DonePoll
  API2-->>E: success { id }
  E-->>Host: onSuccess(id)
  Host->>R: navigate(/table/.../end/vote/result)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • useon
  • i-meant-to-be
  • eunwoo-levi

Poem

토론 끝, 발끝 톡—QR이 반짝!
토끼는 폴을 열고 표를 살짝 맡겨요.
찬성? 반대? 바가 슝—숫자는 깡총!
왕관은 주인을 찾고, 박수는 콩콩.
결과 창 열리면, 집으로 냠—점프! 🐰✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning tailwind.config.js에 brandBackground 유틸리티를 추가했으나 투표 기능 구현과 직접적인 연관이 없어 이 PR의 범위를 벗어나는 변경으로 보입니다. 해당 Tailwind 설정 변경을 별도 PR로 분리하거나 해당 변경이 실제로 필요한지 재검토해 주세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 제목 “[FEAT] 승패 투표 기능 추가”는 PR의 주요 변경사항인 승패 투표 기능 추가를 명확하고 간결하게 요약하고 있어 협업 시 변경 내역을 쉽게 파악할 수 있습니다.
Linked Issues Check ✅ Passed PR은 #381 이슈에서 요구한 투표 생성, 참여 흐름, 결과 확인 UI 및 관련 컴포넌트(투표 생성 버튼, QR 코드 페이지, 참여 페이지, 결과 페이지, 세부 결과 모달 등)를 모두 포함하고 있어 이슈 목표를 충족합니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b6c228 and a08add3.

📒 Files selected for processing (2)
  • src/hooks/mutations/useFetchEndPoll.ts (1 hunks)
  • src/page/DebateVotePage/DebateVotePage.tsx (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 3

🧹 Nitpick comments (3)
src/components/icons/Check-Box.tsx (1)

33-33: clipPath ID 고유성 고려 (선택사항)

하드코딩된 clipPath ID가 동일 페이지에 여러 인스턴스가 렌더링될 경우 이론적으로 충돌할 수 있습니다. 실무에서는 대부분 문제없지만, 고유 ID를 보장하려면 useId() hook을 사용할 수 있습니다.

예시:

+import { useId } from 'react';
+
 export default function CheckBox({
   color = 'currentColor',
   className = '',
   ...props
 }: IconProps) {
+  const clipId = useId();
   return (
     <svg
       ...
     >
-      <g clipPath="url(#clip0_2060_9314)">
+      <g clipPath={`url(#${clipId})`}>
         ...
       </g>
       <defs>
-        <clipPath id="clip0_2060_9314">
+        <clipPath id={clipId}>
           ...
         </clipPath>
       </defs>
     </svg>
   );
 }
src/page/VoteCompletePage/VoteCompletePage.tsx (1)

11-11: 색상값 상수화 고려 (선택사항)

#FECD4C 색상이 하드코딩되어 있습니다. 이 색상이 다른 곳에서도 사용되거나 일관성 있는 테마 관리가 필요한 경우, 색상 상수로 추출하는 것을 고려해보세요.

예시:

// constants/colors.ts
export const COLORS = {
  SUCCESS: '#FECD4C',
  // ...
};

// VoteCompletePage.tsx
import { COLORS } from '../../constants/colors';
// ...
<CheckBox color={COLORS.SUCCESS} />
src/type/type.ts (1)

80-84: 'PROS' | 'CONS' 유니언을 한 곳으로 모아주세요.

이미 파일 상단에 동일한 리터럴 유니언을 표현한 TimeBasedStance와 이번에 추가한 TeamKey가 존재하는데, 여기에서 다시 직접 작성하면 상태값 변경 시 누락 위험이 커집니다. VoterPollInfo도 기존 타입을 재사용하도록 맞춰주시면 유지보수가 한결 수월해요.

 export interface VoterPollInfo {
   name: string;
   participateCode: string;
-  team: 'PROS' | 'CONS';
+  team: TeamKey;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7cffb and 9030215.

⛔ Files ignored due to path filters (1)
  • src/assets/debateEnd/crown.svg is excluded by !**/*.svg
📒 Files selected for processing (23)
  • src/apis/apis/poll.ts (1 hunks)
  • src/apis/endpoints.ts (1 hunks)
  • src/apis/responses/poll.ts (1 hunks)
  • src/components/icons/Check-Box.tsx (1 hunks)
  • src/hooks/mutations/useCreatePoll.ts (1 hunks)
  • src/hooks/mutations/useFetchEndpoll.ts (1 hunks)
  • src/hooks/mutations/usePostVoterPollInfo.ts (1 hunks)
  • src/hooks/query/useGetPollInfo.ts (1 hunks)
  • src/hooks/query/useGetVoterPollInfo.ts (1 hunks)
  • src/layout/components/footer/StickyFooterWrapper.tsx (1 hunks)
  • src/page/DebateEndPage/DebateEndPage.tsx (2 hunks)
  • src/page/DebateVotePage/DebateVotePage.tsx (1 hunks)
  • src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/CountUp.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/VoteDetailResult.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/VoteResultRow.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/WinnerCard.tsx (1 hunks)
  • src/page/VoteCompletePage/VoteCompletePage.tsx (1 hunks)
  • src/page/VoteParticipationPage/VoteParticipationPage.tsx (1 hunks)
  • src/page/VoteParticipationPage/components/VoteTeamButton.tsx (1 hunks)
  • src/routes/routes.tsx (2 hunks)
  • src/type/type.ts (2 hunks)
  • tailwind.config.js (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
src/hooks/mutations/usePostVoterPollInfo.ts (3)
src/hooks/mutations/usePreventDuplicateMutation.ts (1)
  • usePreventDuplicateMutation (9-52)
src/type/type.ts (1)
  • VoterPollInfo (80-84)
src/apis/apis/poll.ts (1)
  • postVoterPollInfo (88-101)
src/hooks/query/useGetPollInfo.ts (2)
src/apis/responses/poll.ts (1)
  • GetPollResponseType (9-11)
src/apis/apis/poll.ts (1)
  • getPollInfo (45-56)
src/page/VoteCompletePage/VoteCompletePage.tsx (1)
src/components/icons/Check-Box.tsx (1)
  • CheckBox (3-46)
src/page/DebateVotePage/DebateVotePage.tsx (3)
src/hooks/query/useGetPollInfo.ts (1)
  • useGetPollInfo (5-14)
src/hooks/mutations/useFetchEndpoll.ts (1)
  • useFetchEndpoll (5-12)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/hooks/mutations/useFetchEndpoll.ts (3)
src/hooks/mutations/usePreventDuplicateMutation.ts (1)
  • usePreventDuplicateMutation (9-52)
src/apis/apis/poll.ts (1)
  • patchEndPoll (59-70)
src/apis/responses/poll.ts (1)
  • PatchPollResponseType (14-16)
src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (6)
src/hooks/query/useGetPollInfo.ts (1)
  • useGetPollInfo (5-14)
src/hooks/useModal.tsx (1)
  • useModal (15-87)
src/type/type.ts (1)
  • TeamKey (113-113)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/page/DebateVoteResultPage/components/WinnerCard.tsx (1)
  • WinnerCard (10-54)
src/page/DebateVoteResultPage/components/VoteDetailResult.tsx (1)
  • VoteDetailResult (11-58)
src/routes/routes.tsx (4)
src/page/DebateVotePage/DebateVotePage.tsx (1)
  • DebateVotePage (8-122)
src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (1)
  • DebateVoteResultPage (10-120)
src/page/VoteParticipationPage/VoteParticipationPage.tsx (1)
  • VoteParticipationPage (21-163)
src/page/VoteCompletePage/VoteCompletePage.tsx (1)
  • VoteCompletePage (4-21)
src/components/icons/Check-Box.tsx (1)
src/components/icons/IconProps.tsx (1)
  • IconProps (3-6)
src/page/VoteParticipationPage/components/VoteTeamButton.tsx (1)
src/type/type.ts (2)
  • TeamKey (113-113)
  • TEAM_STYLE (114-127)
src/apis/responses/poll.ts (1)
src/type/type.ts (3)
  • CreatePollInfo (68-72)
  • PollInfo (73-78)
  • VoterPollInfo (80-84)
src/page/DebateVoteResultPage/components/WinnerCard.tsx (1)
src/type/type.ts (2)
  • TeamKey (113-113)
  • TEAM_STYLE (114-127)
src/hooks/mutations/useCreatePoll.ts (3)
src/hooks/mutations/usePreventDuplicateMutation.ts (1)
  • usePreventDuplicateMutation (9-52)
src/apis/apis/poll.ts (1)
  • postCreatePoll (30-42)
src/apis/responses/poll.ts (1)
  • PostCreatePollResponseType (4-6)
src/page/DebateVoteResultPage/components/VoteResultRow.tsx (2)
src/type/type.ts (2)
  • TeamKey (113-113)
  • TEAM_STYLE (114-127)
src/page/DebateVoteResultPage/components/CountUp.tsx (1)
  • CountUp (12-31)
src/hooks/query/useGetVoterPollInfo.ts (2)
src/apis/responses/poll.ts (1)
  • GetVoterPollInfoResponseType (19-22)
src/apis/apis/poll.ts (1)
  • getVoterPollInfo (73-85)
src/page/DebateEndPage/DebateEndPage.tsx (2)
src/hooks/mutations/useCreatePoll.ts (1)
  • useCreatePoll (5-12)
src/components/GoToHomeButton/GoToHomeButton.tsx (1)
  • GoToHomeButton (3-20)
src/apis/apis/poll.ts (4)
src/apis/responses/poll.ts (5)
  • PostCreatePollResponseType (4-6)
  • GetPollResponseType (9-11)
  • PatchPollResponseType (14-16)
  • GetVoterPollInfoResponseType (19-22)
  • PostVoterPollInfoResponseType (26-28)
src/apis/endpoints.ts (1)
  • ApiUrl (8-14)
src/apis/primitives.ts (1)
  • request (22-56)
src/type/type.ts (1)
  • VoterPollInfo (80-84)
src/page/VoteParticipationPage/VoteParticipationPage.tsx (9)
src/type/type.ts (1)
  • TeamKey (113-113)
src/hooks/query/useGetVoterPollInfo.ts (1)
  • useGetVoterPollInfo (5-10)
src/hooks/useModal.tsx (1)
  • useModal (15-87)
src/hooks/mutations/usePostVoterPollInfo.ts (1)
  • usePostVoterPollInfo (5-18)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/components/LoadingIndicator/LoadingIndicator.tsx (1)
  • LoadingIndicator (4-13)
src/components/ClearableInput/ClearableInput.tsx (1)
  • ClearableInput (11-37)
src/page/VoteParticipationPage/components/VoteTeamButton.tsx (1)
  • VoteTeamButton (13-57)
src/components/DialogModal/DialogModal.tsx (1)
  • DialogModal (16-67)
🔇 Additional comments (7)
tailwind.config.js (1)

34-36: 브랜드 배경 그라데이션 유틸리티 승인

bg-brandBackground가 DebateVotePage와 DebateVoteResultPage에서 정상적으로 사용되고 있으며, 설정 방식이 적절합니다.

src/layout/components/footer/StickyFooterWrapper.tsx (1)

7-7: 변경사항 승인

justify-center 추가로 footer 콘텐츠가 수평 중앙 정렬됩니다. 새로운 투표 UI 레이아웃과 일관성 있게 잘 통합되었습니다.

src/page/DebateVoteResultPage/components/CountUp.tsx (1)

12-31: LGTM!

애니메이션 로직이 깔끔하게 구현되었습니다:

  • useEffect의 의존성 배열이 정확함
  • controls.stop()으로 언마운트 시 메모리 누수 방지
  • Math.round()로 정수 표시 보장
src/page/DebateVoteResultPage/components/WinnerCard.tsx (1)

10-54: LGTM!

승자 카드 컴포넌트가 잘 구현되었습니다:

  • null teamkey 처리가 안전함
  • 조건부 스타일링이 명확함
  • 무승부 시 왕관을 표시하지 않는 로직이 직관적임
src/apis/endpoints.ts (1)

13-13: LGTM!

poll 엔드포인트가 기존 패턴과 일관성 있게 추가되었습니다.

src/hooks/query/useGetVoterPollInfo.ts (1)

5-10: LGTM!

react-query 훅이 표준 패턴대로 잘 구현되었습니다:

  • queryKey에 pollId 포함으로 적절한 캐싱
  • 타입 안정성 확보
src/hooks/mutations/useFetchEndpoll.ts (1)

5-12: LGTM!

중복 요청 방지 패턴이 올바르게 적용되었습니다:

  • usePreventDuplicateMutation 래퍼 사용
  • 타입 안정성 확보
  • onSuccess 콜백 시그니처 일치

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

🧹 Nitpick comments (2)
src/page/DebateVotePage/DebateVotePage.stories.tsx (1)

17-23: 다른 스토리 파일과 동일한 래퍼 패턴이 중복됩니다.

DebateVoteResultPage.stories.tsx, VoteCompletePage.stories.tsx, VoteParticipationPage.stories.tsx에서 동일한 전체 뷰포트 래퍼 패턴을 사용하고 있습니다. 공통 데코레이터로 추출하면 유지보수가 개선됩니다.

.storybook/decorators.tsx 같은 공통 파일을 만들어 데코레이터로 추출하는 것을 권장합니다:

// .storybook/decorators.tsx
export const FullScreenDecorator = (Story: React.ComponentType) => (
  <div style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}>
    <Story />
  </div>
);

그런 다음 각 스토리에서 다음과 같이 사용:

+import { FullScreenDecorator } from '../../.storybook/decorators';

 const meta: Meta<typeof DebateVotePage> = {
   title: 'page/DebateVotePage',
   component: DebateVotePage,
   tags: ['autodocs'],
   parameters: {
     layout: 'fullscreen',
   },
+  decorators: [FullScreenDecorator],
 };

 export default meta;

 type Story = StoryObj<typeof DebateVotePage>;

-export const Default: Story = {
-  render: () => (
-    <div style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}>
-      <DebateVotePage />
-    </div>
-  ),
-};
+export const Default: Story = {};
src/mocks/handlers/poll.ts (1)

15-90: 선택적 개선 사항: 하드코딩된 값 및 로깅.

모든 핸들러가 id: 7로 하드코딩되어 있어 다양한 시나리오 테스트가 어려울 수 있습니다. params에서 받은 ID를 사용하거나, 각 핸들러마다 다른 ID를 반환하는 것을 고려하세요.

또한, console.log를 사용한 로깅은 개발 환경에서 유용하지만, 프로덕션 환경에서는 불필요한 노이즈가 될 수 있습니다. 환경변수나 디버그 플래그를 활용하여 조건부 로깅을 고려하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9030215 and 731d70d.

📒 Files selected for processing (5)
  • src/mocks/handlers/poll.ts (1 hunks)
  • src/page/DebateVotePage/DebateVotePage.stories.tsx (1 hunks)
  • src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (1 hunks)
  • src/page/VoteCompletePage/VoteCompletePage.stories.tsx (1 hunks)
  • src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (4)
src/page/VoteParticipationPage/VoteParticipationPage.tsx (1)
  • VoteParticipationPage (21-163)
src/page/DebateVotePage/DebateVotePage.stories.tsx (1)
  • Default (17-23)
src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (1)
  • Default (17-23)
src/page/VoteCompletePage/VoteCompletePage.stories.tsx (1)
  • Default (17-23)
src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (4)
src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (1)
  • DebateVoteResultPage (10-120)
src/page/DebateVotePage/DebateVotePage.stories.tsx (1)
  • Default (17-23)
src/page/VoteCompletePage/VoteCompletePage.stories.tsx (1)
  • Default (17-23)
src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (1)
  • Default (17-23)
src/mocks/handlers/poll.ts (2)
src/apis/endpoints.ts (1)
  • ApiUrl (8-14)
src/apis/responses/poll.ts (3)
  • PostCreatePollResponseType (4-6)
  • PatchPollResponseType (14-16)
  • PostVoterPollInfoResponseType (26-28)
src/page/DebateVotePage/DebateVotePage.stories.tsx (4)
src/page/DebateVotePage/DebateVotePage.tsx (1)
  • DebateVotePage (8-122)
src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (1)
  • Default (17-23)
src/page/VoteCompletePage/VoteCompletePage.stories.tsx (1)
  • Default (17-23)
src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (1)
  • Default (17-23)
src/page/VoteCompletePage/VoteCompletePage.stories.tsx (4)
src/page/VoteCompletePage/VoteCompletePage.tsx (1)
  • VoteCompletePage (4-21)
src/page/DebateVotePage/DebateVotePage.stories.tsx (1)
  • Default (17-23)
src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (1)
  • Default (17-23)
src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (1)
  • Default (17-23)
🔇 Additional comments (4)
src/page/VoteCompletePage/VoteCompletePage.stories.tsx (1)

1-23: LGTM! Storybook 스토리 구성이 올바릅니다.

VoteCompletePage의 Storybook 스토리가 잘 구성되어 있습니다:

  • Meta 설정이 올바르고 autodocs 태그와 fullscreen 레이아웃이 적절하게 적용되었습니다.
  • Default 스토리의 전체 화면 컨테이너 패턴이 다른 투표 관련 페이지들(DebateVotePage, DebateVoteResultPage, VoteParticipationPage)과 일관되게 적용되었습니다.
  • VoteCompletePage 컴포넌트를 Storybook 캔버스에서 올바르게 렌더링할 수 있도록 구현되었습니다.
src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx (1)

1-11: LGTM!

Storybook 메타 설정이 올바르게 구성되어 있습니다. fullscreen 레이아웃은 페이지 컴포넌트에 적합합니다.

src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx (2)

1-2: LGTM!

임포트가 올바르게 구성되어 있습니다.


17-23: 스토리에 라우트 파라미터 설정 확인

스토리 정의는 다른 투표 관련 페이지 스토리들과 일관된 패턴을 따르고 있습니다. 다만, VoteParticipationPage는 URL 파라미터 pollId에 의존하므로, 스토리별 라우트 파라미터를 설정하는 것이 좋습니다.

메타 설정의 MSW 핸들러 추가 후, 다음과 같이 스토리별 파라미터를 추가하는 것을 고려하세요:

export const Default: Story = {
  parameters: {
    reactRouter: reactRouterParameters({
      routing: { path: '/vote/:id', params: { id: '1' } },
    }),
  },
  render: () => (
    <div style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}>
      <VoteParticipationPage />
    </div>
  ),
};

이렇게 하면 스토리북에서 특정 pollId로 테스트할 수 있습니다.

});
}),

// GET /api/polls/{pollId}/votes
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

주석의 HTTP 메서드 수정 필요.

주석에 "GET"으로 표기되어 있지만 실제로는 DELETE 핸들러입니다.

다음 diff를 적용하여 수정하세요:

-  // GET /api/polls/{pollId}/votes
+  // DELETE /api/polls/{pollId}/votes
   http.delete(ApiUrl.poll + `/:pollId/votes`, ({ params }) => {
🤖 Prompt for AI Agents
In src/mocks/handlers/poll.ts around line 60, the comment above the handler
incorrectly states "GET /api/polls/{pollId}/votes" while the implemented handler
is a DELETE; update the comment to read "DELETE /api/polls/{pollId}/votes" (or
the exact DELETE route signature used) so the comment matches the actual HTTP
method and endpoint.

Copy link
Contributor

@i-meant-to-be i-meant-to-be left a comment

Choose a reason for hiding this comment

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

고생 많으셨습니다! 리뷰 요청 사항에서 부탁하신 부분도 의견 남겨두었구요. 제 환경에서는 기능 잘 동작하는 것 같습니다.

리뷰 내용이 좀 많습니다. 또한, 직접 수정을 요청드리는 권장, 대안 수준 외에도 의견과 질문도 섞여 있어서, 세심히 봐 주시면 정말 감사하겠습니다...!

Comment on lines +14 to +17
const TEAM_LABEL = {
PROS: '찬성팀',
CONS: '반대팀',
} as const;
Copy link
Contributor

@i-meant-to-be i-meant-to-be Oct 7, 2025

Choose a reason for hiding this comment

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

의견: 진영이 반드시 찬성이나 반대로만 나뉘는가?

서비스에 팀 이름을 직접 입력할 수 있게 하는 변경 사항을 도입한 큰 이유 중 하나는, 토론 주제가 찬성 또는 반대로만 나뉘지 않을 가능성도 있기 때문이라고 봅니다. 예를 들어 짜장 vs. 짬뽕, 선별적 복지 vs. 보편적 복지 등의 주제들처럼요. 그러나 이 레이블들은 현재 찬성 팀과 반대 팀으로만 양쪽을 양분하고 있습니다.

다시 말하면, 투표 기능은 모든 주제가 찬반으로 나뉠 거라는 의도 하에 개발되었으나, 테이블 생성 시 팀명은 찬반으로 나뉘지 않을 가능성도 고려하여 개발되었고, 이 두 기능의 의도 사이에 간극이 존재한다는 점입니다.

다만, 이 부분은 단순히 기술적으로 나눌 내용은 아니기 때문에, 향후 회의에서 의견 수렴을 받고 개선하는 방향이 좋아 보입니다. 따라서, 권장이나 대안으로 올리지 않고 단순 의견의 형태로 댓글을 남깁니다. 역시 기억만 해 둡시다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

그럼 찬성 반대가 아니라 그냥 팀 이름만 남겨두는 방식으로 두는 것도 좋은 방식이겠네요. 확인했습니다.

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: 2

♻️ Duplicate comments (3)
src/page/DebateEndPage/DebateEndPage.tsx (2)

47-51: 동일 카드 스타일 클래스 추출 (중복 제거)

두 버튼의 className이 완전히 동일합니다. 과거 코멘트와 동일 제안: 공통 menuCardClasses로 분리하세요.

예시:

 export default function DebateEndPage() {
+  const menuCardClasses =
+    "flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11";
...
-            <button
+            <button
               onClick={handleFeedbackClick}
-              className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
+              className={menuCardClasses}
             >
...
-            <button
+            <button
               onClick={() => mutate(Number(tableId))}
-              className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
+              className={menuCardClasses}
             >

Also applies to: 65-69


45-63: 두 카드의 타이포그래피·간격 구조 통일

첫 카드(h2+p)와 두 번째 카드(div 래퍼 안 p+p) 구조 차이로 세로 간격이 어긋날 수 있습니다. 동일한 마크업 계층(h2+p) 또는 동일한 래퍼 사용으로 통일해 주세요.

Also applies to: 74-81

src/type/type.ts (1)

113-114: 중복 타입 통일: TeamKey ↔ TimeBasedStance

TeamKey = 'PROS' | 'CONS'TimeBasedStance와 동일합니다. 하나로 합치거나 export type TeamKey = TimeBasedStance;로 별칭화해 중복을 제거하세요.

-export type TeamKey = 'PROS' | 'CONS';
+export type TeamKey = TimeBasedStance;
🧹 Nitpick comments (3)
src/hooks/mutations/useCreatePoll.ts (1)

5-11: 이름·파라미터 명 확정성 높이기 (혼동 방지)

  • 파일명(useCreatePoll.ts)과 기본 익스포트명(usePostPoll)이 불일치합니다. 하나로 맞추는 편이 좋습니다.
  • mutationFn: (id: number)id는 실질적으로 tableId입니다. 명시적으로 바꿔 가독성 올리는 것을 권장합니다.

적용 예시:

-export default function usePostPoll(onSuccess: (id: number) => void) {
+export default function useCreatePoll(onSuccess: (id: number) => void) {
   return usePreventDuplicateMutation({
-    mutationFn: (id: number) => postPoll(id),
+    mutationFn: (tableId: number) => postPoll(tableId),
     onSuccess: (response: PostPollResponseType) => {
       onSuccess(response.id);
     },
   });
 }
src/apis/apis/poll.ts (1)

29-40: 전반적으로 적절합니다. URL 조합 헬퍼로 중복만 줄이면 좋겠습니다.

기능적으로 문제 없어 보입니다. requestUrl + \/${id}`` 패턴이 반복되니 소규모 유틸로 중복 제거를 고려해 보세요.

예시:

const withId = (base: string, ...segs: (string|number)[]) =>
  [base, ...segs].map(String).join('/');

// 사용
await request('GET', withId(ApiUrl.poll, pollId), null, null);
await request('GET', withId(ApiUrl.poll, pollId, 'votes'), null, null);

Also applies to: 42-54, 56-68, 70-83, 85-99

src/type/type.ts (1)

85-86: export 주석 배치 정리 (가독성)

export // comment 형태 대신 한 줄에 export type TimerBGState = ...로 두는 편이 안전·명료합니다.

-export // ===== 배경 색상 상태 타입 및 컬러 맵 정의 =====
-type TimerBGState = 'default' | 'warning' | 'danger' | 'expired';
+// ===== 배경 색상 상태 타입 및 컬러 맵 정의 =====
+export type TimerBGState = 'default' | 'warning' | 'danger' | 'expired';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 731d70d and ee9983a.

📒 Files selected for processing (6)
  • src/apis/apis/poll.ts (1 hunks)
  • src/apis/responses/poll.ts (1 hunks)
  • src/hooks/mutations/useCreatePoll.ts (1 hunks)
  • src/mocks/handlers/poll.ts (1 hunks)
  • src/page/DebateEndPage/DebateEndPage.tsx (2 hunks)
  • src/type/type.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/mocks/handlers/poll.ts
🧰 Additional context used
🧬 Code graph analysis (4)
src/page/DebateEndPage/DebateEndPage.tsx (2)
src/hooks/mutations/useCreatePoll.ts (1)
  • usePostPoll (5-12)
src/components/GoToHomeButton/GoToHomeButton.tsx (1)
  • GoToHomeButton (3-20)
src/apis/responses/poll.ts (1)
src/type/type.ts (3)
  • BasePollInfo (68-72)
  • PollInfo (73-78)
  • VoterPollInfo (80-84)
src/hooks/mutations/useCreatePoll.ts (3)
src/hooks/mutations/usePreventDuplicateMutation.ts (1)
  • usePreventDuplicateMutation (9-52)
src/apis/apis/poll.ts (1)
  • postPoll (30-40)
src/apis/responses/poll.ts (1)
  • PostPollResponseType (4-6)
src/apis/apis/poll.ts (4)
src/apis/responses/poll.ts (5)
  • PostPollResponseType (4-6)
  • GetPollResponseType (9-11)
  • PatchPollResponseType (14-16)
  • GetVoterPollInfoResponseType (19-22)
  • PostVoterPollInfoResponseType (26-28)
src/apis/endpoints.ts (1)
  • ApiUrl (8-14)
src/apis/primitives.ts (1)
  • request (22-56)
src/type/type.ts (1)
  • VoterPollInfo (80-84)
🔇 Additional comments (2)
src/type/type.ts (1)

68-84: 투표 모델 타입 추가 LGTM

BasePollInfo, PollInfo, VoterPollInfo 추가 구조 깔끔합니다. 다른 응답 타입들과도 일관적입니다.

src/page/DebateEndPage/DebateEndPage.tsx (1)

18-20: 투표 라우트 경로 일치 여부 수동 확인 필요
navigate('/table/customize/${pollId}/end/vote')가 PR 안내 가이드(/vote/{pollId}) 및 실제 라우터 정의와 일치하는지 직접 확인해주세요.

Comment on lines 66 to 67
onClick={() => mutate(Number(tableId))}
className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

tableId 파싱 가드 추가 필요 (NaN 전파 방지)

Number(tableId)NaN일 수 있어 /polls/NaN 호출 위험이 있습니다. 클릭 핸들러/버튼 비활성화로 가드하세요.

적용 예시:

 export default function DebateEndPage() {
   const { id: tableId } = useParams();
   const navigate = useNavigate();
+  const tableIdNum = Number(tableId);
+  const isValidTableId = Number.isFinite(tableIdNum);

   const handleFeedbackClick = () => {
     navigate(`/table/customize/${tableId}/end/feedback`);
   };

   const handleVoteClick = (pollId: number) => {
     navigate(`/table/customize/${pollId}/end/vote`);
   };
   const { mutate } = usePostPoll(handleVoteClick);
...
-            <button
-              onClick={() => mutate(Number(tableId))}
+            <button
+              disabled={!isValidTableId}
+              aria-disabled={!isValidTableId}
+              onClick={() => isValidTableId && mutate(tableIdNum)}
               className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
             >
📝 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
onClick={() => mutate(Number(tableId))}
className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
export default function DebateEndPage() {
const { id: tableId } = useParams();
const navigate = useNavigate();
const tableIdNum = Number(tableId);
const isValidTableId = Number.isFinite(tableIdNum);
const handleFeedbackClick = () => {
navigate(`/table/customize/${tableId}/end/feedback`);
};
const handleVoteClick = (pollId: number) => {
navigate(`/table/customize/${pollId}/end/vote`);
};
const { mutate } = usePostPoll(handleVoteClick);
// … other JSX …
<button
disabled={!isValidTableId}
aria-disabled={!isValidTableId}
onClick={() => isValidTableId && mutate(tableIdNum)}
className="flex h-[280px] w-[280px] flex-col items-center justify-center gap-6 rounded-[34px] border-2 border-default-disabled/hover bg-white transition-all duration-300 hover:border-brand/40 hover:shadow-[0px_0px_22px_6px_#FECD4C63] md:h-[300px] md:w-[300px] lg:h-[340px] lg:w-[340px] xl:h-[370px] xl:w-[370px] xl:gap-11"
>
{/* … card content … */}
</button>
}
🤖 Prompt for AI Agents
In src/page/DebateEndPage/DebateEndPage.tsx around lines 66-67, Number(tableId)
can produce NaN and cause a request to /polls/NaN; add a parsing guard:
parse/convert tableId to a number once, check Number.isFinite or !Number.isNaN
(and optionally > 0) before calling mutate, disable the clickable element (or
return early in onClick) when the parsed value is invalid, and ensure the
element's disabled state or aria-disabled is set so users can't trigger the
handler while tableId is not a valid number.

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/components/icons/CheckBox.tsx (1)

11-32: 구현이 올바르며, Tailwind 일관성을 위한 개선 고려를 권장합니다.

현재 구현은 정상적으로 작동하며 clsx를 적절히 활용하고 있습니다. 다만, SVG의 fill 속성이 실제 색상 값(hex/rgb)을 요구하므로 Tailwind 클래스를 직접 사용할 수 없는 구조적 제약이 있습니다.

향후 코드베이스의 Tailwind 우선 원칙과 일관성을 높이려면, CSS 변수나 Tailwind의 색상 값을 추출하는 방식으로 checkColor가 Tailwind 클래스를 지원하도록 리팩토링을 고려해볼 수 있습니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50437c1 and a33ce25.

📒 Files selected for processing (2)
  • src/components/icons/CheckBox.tsx (1 hunks)
  • src/page/VoteCompletePage/VoteCompletePage.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/page/VoteCompletePage/VoteCompletePage.tsx (1)
src/components/icons/CheckBox.tsx (1)
  • CheckBox (11-32)
src/components/icons/CheckBox.tsx (1)
src/components/icons/Check.tsx (1)
  • DTCheck (3-27)
🔇 Additional comments (2)
src/components/icons/CheckBox.tsx (1)

4-9: 인터페이스 정의가 명확합니다.

props 타입이 적절하게 정의되어 있습니다. checkColor가 문자열 타입이므로 hex 색상 값을 받을 수 있습니다.

src/page/VoteCompletePage/VoteCompletePage.tsx (1)

4-21: 구현이 깔끔하며 정상적으로 작동합니다.

투표 완료 페이지가 명확하게 구현되어 있습니다. Line 11에서 checkColor를 지정하지 않아 기본값인 흰색(#FFFFFF)이 사용되며, className="bg-brand"로 설정된 브랜드 배경색 위에 흰색 체크마크가 표시되는 것이 시각적으로 적절합니다.

과거 리뷰 코멘트에서 언급된 Tailwind 클래스 사용 권장 사항은 CheckBox 컴포넌트의 SVG 구조상 현재는 적용하기 어려운 상황이며, 향후 CheckBox 컴포넌트가 Tailwind 클래스를 지원하도록 개선되면 함께 반영하는 것을 고려할 수 있습니다.

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 (2)
src/components/icons/CheckBox.tsx (1)

19-20: 여러 text- 클래스가 있을 때의 동작을 고려하세요.*

현재 구현은 첫 번째 text-* 클래스만 추출합니다. 일반적으로는 문제없지만, 만약 className에 여러 개의 text-* 클래스가 포함된 경우(예: "text-red-500 text-lg") 의도하지 않은 결과가 발생할 수 있습니다.

대부분의 경우 색상 클래스는 하나만 사용되므로 현재 구현으로도 충분하지만, 필요하다면 다음과 같이 모든 text-* 클래스를 추출할 수도 있습니다:

-  const textClass =
-    className.split(' ').find((c) => c.startsWith('text-')) ?? '';
+  const textClass = className
+    .split(' ')
+    .filter((c) => c.startsWith('text-'))
+    .join(' ');
src/page/VoteCompletePage/VoteCompletePage.tsx (1)

11-15: 체크박스 사용이 적절하며, 작은 개선 가능성이 있습니다.

className을 통한 색상 지정은 과거 리뷰 피드백을 잘 반영했습니다.

작은 개선사항:

  • Line 13: checked={true} 대신 checked만 사용해도 됩니다 (boolean 속성).
-            <CheckBox
-              className="bg-brand text-white"
-              checked={true}
-              size={'4vw'}
-            />
+            <CheckBox
+              className="bg-brand text-white"
+              checked
+              size={'4vw'}
+            />

Based on learnings

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a33ce25 and 6211fdf.

📒 Files selected for processing (2)
  • src/components/icons/CheckBox.tsx (1 hunks)
  • src/page/VoteCompletePage/VoteCompletePage.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/icons/CheckBox.tsx (1)
src/components/icons/Check.tsx (1)
  • DTCheck (3-27)
src/page/VoteCompletePage/VoteCompletePage.tsx (1)
src/components/icons/CheckBox.tsx (1)
  • CheckBox (13-37)
🔇 Additional comments (3)
src/components/icons/CheckBox.tsx (2)

4-8: 인터페이스가 명확하고 적절합니다.

props 정의가 명확하며 컴포넌트 사용 패턴과 잘 맞습니다.


22-36: 렌더링 로직이 적절합니다.

컨테이너와 아이콘의 스타일링이 올바르게 구현되어 있습니다. text-* 클래스가 컨테이너와 DTCheck 모두에 전달되는 것은 색상 상속을 위한 의도된 동작으로 보입니다.

src/page/VoteCompletePage/VoteCompletePage.tsx (1)

4-25: 페이지 구조가 명확하고 적절합니다.

레이아웃과 스타일링이 투표 완료 페이지의 목적에 잘 맞습니다. 반응형 텍스트 크기 조정도 적절하게 구현되어 있습니다.

@jaeml06 jaeml06 requested a review from i-meant-to-be October 8, 2025 06:24
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/page/DebateVotePage/DebateVotePage.tsx (1)

13-19: QR 코드 URL이 개발 환경에서 여전히 잘못 생성됩니다

이전 리뷰에서 지적된 Critical 이슈가 아직 수정되지 않았습니다. 개발 모드(import.meta.env.MODE !== 'production')에서 baseUrlundefined로 설정되어 QR 코드에 "undefined/vote/{pollId}" 형태의 잘못된 URL이 인코딩됩니다. 로컬 개발 및 QA 환경에서 QR 스캔이 전부 실패하므로 즉시 수정이 필요합니다.

아래 diff를 적용하여 환경별로 올바른 base URL을 설정하고, pollId가 유효하지 않을 때는 빈 문자열을 반환하도록 수정하세요:

-  const baseUrl =
-    import.meta.env.MODE !== 'production'
-      ? undefined
-      : import.meta.env.VITE_SHARE_BASE_URL;
+  const shareBaseUrl =
+    import.meta.env.MODE === 'production'
+      ? import.meta.env.VITE_SHARE_BASE_URL ?? ''
+      : typeof window !== 'undefined'
+        ? window.location.origin
+        : '';
+
   const voteUrl = useMemo(() => {
+    if (!pollId || Number.isNaN(pollId)) return '';
-    return `${baseUrl}/vote/${pollId}`;
+    return `${shareBaseUrl}/vote/${pollId}`;
-  }, [baseUrl, pollId]);
+  }, [shareBaseUrl, pollId]);
🧹 Nitpick comments (2)
src/hooks/query/useGetVoterPollInfo.ts (1)

1-14: 전체적으로 잘 구현되었습니다!

React Query 훅이 올바르게 구현되었고 타입 안정성도 확보되어 있습니다. 몇 가지 선택적 개선사항을 제안드립니다:

  1. 쿼리 설정 옵션 추가 고려: staleTime, retry 등의 설정을 명시적으로 추가하면 일관성과 예측 가능성이 향상됩니다.
  2. refetchOnWindowFocus 설정: 투표 정보의 특성상 윈도우 포커스 시 자동 재조회가 필요한지 검토해보세요.

선택적으로 다음과 같이 쿼리 설정을 추가할 수 있습니다:

 export function useGetVoterPollInfo(
   pollId: number,
   options?: { enabled?: boolean },
 ) {
   return useQuery<GetVoterPollInfoResponseType>({
     queryKey: ['VoterPoll', pollId],
     queryFn: () => getVoterPollInfo(pollId),
     enabled: options?.enabled,
+    staleTime: 5 * 60 * 1000, // 5분
+    retry: 1,
+    refetchOnWindowFocus: false,
   });
 }
src/page/DebateVotePage/DebateVotePage.tsx (1)

28-40: 초기 로딩 상태에 대한 UI 피드백 추가를 권장합니다

현재 isLoading 상태를 추적하고 있지만, 초기 데이터 fetch 중에는 빈 화면이 표시됩니다. 사용자 경험 개선을 위해 초기 로딩 시 스켈레톤 UI나 스피너를 표시하는 것을 고려해 보세요.

예를 들어, lines 42-50의 에러 처리 전에 로딩 체크를 추가할 수 있습니다:

if (isFetching && !data) {
  return (
    <DefaultLayout>
      <DefaultLayout.ContentContainer>
        <div className="flex h-full items-center justify-center">
          <LoadingSpinner />
        </div>
      </DefaultLayout.ContentContainer>
    </DefaultLayout>
  );
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6211fdf and d9258e9.

📒 Files selected for processing (5)
  • src/hooks/query/useGetPollInfo.ts (1 hunks)
  • src/hooks/query/useGetVoterPollInfo.ts (1 hunks)
  • src/page/DebateVotePage/DebateVotePage.tsx (1 hunks)
  • src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (1 hunks)
  • src/page/VoteParticipationPage/VoteParticipationPage.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/hooks/query/useGetPollInfo.ts
  • src/page/DebateVoteResultPage/DebateVoteResultPage.tsx
  • src/page/VoteParticipationPage/VoteParticipationPage.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/page/DebateVotePage/DebateVotePage.tsx (3)
src/hooks/query/useGetPollInfo.ts (1)
  • useGetPollInfo (5-15)
src/hooks/mutations/useFetchEndpoll.ts (1)
  • useFetchEndpoll (5-12)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/hooks/query/useGetVoterPollInfo.ts (2)
src/apis/responses/poll.ts (1)
  • GetVoterPollInfoResponseType (19-22)
src/apis/apis/poll.ts (1)
  • getVoterPollInfo (71-83)
🔇 Additional comments (1)
src/page/DebateVotePage/DebateVotePage.tsx (1)

91-105: 참여자 목록의 빈 상태 처리가 잘 구현되었습니다

로딩 상태와 데이터 존재 여부를 명확하게 구분하여 적절한 메시지를 표시하고 있습니다. 사용자가 현재 상태를 쉽게 이해할 수 있습니다.

Comment on lines +110 to +127
<DefaultLayout.StickyFooterWrapper>
<div className="flex w-full max-w-[800px] flex-row items-center justify-center gap-2 bg-default-white">
<button
type="button"
onClick={() => mutate(pollId)}
className="button enabled brand w-full rounded-full"
>
투표 결과 보기
</button>
<button
type="button"
onClick={handleGoHome}
className="button enabled neutral w-full rounded-full"
>
홈으로 돌아가기 →
</button>
</div>
</DefaultLayout.StickyFooterWrapper>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

투표 종료 버튼 동작을 확인하세요

"투표 결과 보기" 버튼을 클릭하면 mutate(pollId)를 호출하여 투표를 종료하고 결과 페이지로 이동합니다. 그러나 다음 사항들을 확인해 주세요:

  1. 투표가 이미 종료된 상태에서 이 페이지에 접근했을 때의 처리
  2. 투표 종료 중(mutate 진행 중) 버튼의 비활성화 또는 로딩 표시
  3. 투표 종료 실패 시 사용자에게 표시할 에러 메시지

현재 코드에서 useFetchEndpoll 훅의 실행 상태를 확인하려면 다음 스크립트를 실행하세요:


🏁 Script executed:

#!/bin/bash
# useFetchEndpoll 훅의 반환 값에 isPending 등의 상태가 포함되어 있는지 확인
rg -nP --type=ts -A 10 'export default function useFetchEndpoll'

Length of output: 601



🏁 Script executed:

#!/bin/bash
# 1. usePreventDuplicateMutation 정의 및 반환 속성 확인
rg -nP --type=ts 'function usePreventDuplicateMutation' -A20

# 2. DebateVotePage.tsx에서 useFetchEndpoll 사용 확인
rg -nP --type=tsx 'useFetchEndpoll' -n src/page/DebateVotePage/DebateVotePage.tsx -C3

Length of output: 1753


투표 종료 버튼 상태 및 오류 처리 구현 필요

  • useFetchEndpollisLoading 상태 동안 “투표 결과 보기” 버튼 비활성화 및 로딩 표시
  • mutation 실패 시 에러 메시지 노출
  • 이미 종료된 투표 접근 시 별도 처리 로직 추가
🤖 Prompt for AI Agents
In src/page/DebateVotePage/DebateVotePage.tsx around lines 110–127, the “투표 결과
보기” button currently always invokes mutate(pollId) and lacks
loading/error/ended-poll handling; disable the button and show a spinner or
loading label while useFetchEndpoll.isLoading is true (add disabled and
aria-busy/aria-disabled attributes), call mutate with onSuccess/onError handlers
(or handle mutation result) to display an error toast/message when the mutation
fails, and detect an already-ended-poll response (status or error code) in
onSuccess/onError to either navigate to the results page or show a specific “이미
종료된 투표입니다” message; ensure UI state is updated (local error state or toast) so
users see failure/ended-poll feedback.

Copy link
Contributor

@i-meant-to-be i-meant-to-be left a comment

Choose a reason for hiding this comment

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

승인 남깁니다! 리뷰 댓글 양이 많았는데 꼼꼼하게 확인해주셔서 감사해요. 이제 기능 및 코드에서 리뷰를 남길 건 없을 듯하고, 별로 중요하지 않은 코멘트 몇 건만 적어뒀으니 시간 날 때 확인해주시면 감사하겠습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

좋은 것 같아요! 파일 이름만 함께 맞춰주면 될 것 같습니다. 반영 감사합니당!

heightClass?: string; // h-20 등 높이 조절용
};

export default function VoteBar({
Copy link
Contributor

Choose a reason for hiding this comment

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

대안: 파일 이름과 컴포넌트명 맞추기

파일 이름은 VoteResultRow고 컴포넌트 이름은 VoteBar이네요. 어떤 쪽이든 둘을 맞춰주면 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 변경했습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

흠 이 부분은 생각보다는 더 엄밀한 고민이 필요하겠네요. 구분법이 꽤 엄격한 편이었네요... 나중에 시간 나면 따져보는 걸로 합시다.

Copy link
Contributor

@useon useon left a comment

Choose a reason for hiding this comment

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

치코 ~!! 작업량이 많았을텐데 꼼꼼하게 챙겨주셔서 감사합니다 ! 몇가지 코멘트 남겼는데 확인해 주셔요 ~!!!! 특히 제 환경에서는 투표 결과를 확인할 수 없어서 이 부분 확인 부탁드립니다 !!!! ^ _^

import { usePreventDuplicateMutation } from './usePreventDuplicateMutation';

export default function usePostPoll(onSuccess: (id: number) => void) {
return usePreventDuplicateMutation({
Copy link
Contributor

Choose a reason for hiding this comment

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

중복호출방지 훅 사용까지 !!! 🥹

import { PatchPollResponseType } from '../../apis/responses/poll';
import { usePreventDuplicateMutation } from './usePreventDuplicateMutation';

export default function useFetchEndpoll(onSuccess: (id: number) => void) {
Copy link
Contributor

Choose a reason for hiding this comment

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

사소한 것이지만 파일명과 함수명이 useFetchEndpoll 이어서 useFetchEndPoll로 수정되어도 될 것 같아요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 수정했습니다.

<DefaultLayout.ContentContainer>
<div
style={backgroundStyle}
className="flex min-h-screen flex-col items-center justify-center p-4 font-pretendard"
Copy link
Contributor

Choose a reason for hiding this comment

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

DefaultLayout이랑 DefaultLayout.ContentContainer을 추가하셨는데 여기 있는 속성이 div의 속성에도 중복으로 들어가 있는 것 같아요 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

중복을 제거했습니다. 다만 해당 컴포넌트가 페이지의 시작점이기 때문에 Layout이라는 구성을 나타갈 필요가 있다고 판단했습니다. 해당 부분을 Laout으로 분리하는 작업이 필요해 보입니다.

Comment on lines 58 to 64
if (isValidPollId) {
return (
<DefaultLayout>
<DefaultLayout.ContentContainer>
<ErrorIndicator onClickRetry={() => navigate('/')}>
유효하지 않은 투표 결과 링크입니다.
</ErrorIndicator>
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 여기 유효하지 않은 투표 결과 링크 표시 조건이 pollId가 유효할 때가 맞나요 ??
테스트시 투표는 정상적으로 되고 실시간으로 확인할 수 있었지만 투표 결과 확인하기 누르면 유효하지 않은 링크라고 떠서 찾아보니 이 조건문이 걸려서요 !!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 조건이 !isValidPollId이어야 하는데 실수 했네요. 다시 작성했습니다.

src/type/type.ts Outdated
participateCode: string;
team: 'PROS' | 'CONS';
}
export // ===== 배경 색상 상태 타입 및 컬러 맵 정의 =====
Copy link
Contributor

Choose a reason for hiding this comment

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

혹시 여기 export 뒤에 선언된 내용이 없는데 의도된 부분일까요 ?!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

제가 지웠다가 실수로 전부 지우지 못했나보네요. 꼼꼼하게 확인 감사합니다.

});

setParticipantName('');
setSelectedTeam(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

어차피 mutate가 성공하면 vote/end 페이지로 이동하게 되어 실제 화면에 반영되지 않는데 초기화 로직을 작성한 이유가 궁금합니다 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

굳이 여기서는 필요없어보이긴합니다. 처음에는 api를 연결하지 않아서 작성했던 로직이 남아있는 것입니다. 현재는 불필요하다고 판단되지만 지우겠습니다.

@jaeml06 jaeml06 requested a review from useon October 11, 2025 14:52
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: 2

♻️ Duplicate comments (3)
src/page/DebateEndPage/DebateEndPage.tsx (1)

58-61: NaN 방지 가드가 여전히 불충분합니다.

현재 if (!tableId) 체크는 tableId가 undefined, null, 빈 문자열일 때만 방어하지만, "abc"와 같은 숫자가 아닌 문자열은 통과시킵니다. 이 경우 Number("abc")는 NaN이 되어 /polls/NaN 같은 잘못된 API 호출이 발생할 수 있습니다.

다음 diff를 적용하여 숫자 유효성을 제대로 검증하세요:

-          onClick={() => {
-            if (!tableId) return; // NaN 방지
-            mutate(Number(tableId));
-          }}
+          onClick={() => {
+            const tableIdNum = Number(tableId);
+            if (!Number.isFinite(tableIdNum) || tableIdNum <= 0) return;
+            mutate(tableIdNum);
+          }}

또는 MenuCard에 disabled prop을 추가할 수 있다면 더 안전합니다:

+        const tableIdNum = Number(tableId);
+        const isValidTableId = Number.isFinite(tableIdNum) && tableIdNum > 0;
+
         <MenuCard
           title="승패투표 진행하기"
           description="QR 코드를 통해 투표 페이지로 이동해요."
           imgSrc={voteStampImage}
           imgAlt="승패투표"
+          disabled={!isValidTableId}
           onClick={() => {
-            if (!tableId) return; // NaN 방지
-            mutate(Number(tableId));
+            if (isValidTableId) {
+              mutate(tableIdNum);
+            }
           }}
           ariaLabel="승패투표 생성 및 진행"
         />
src/page/VoteParticipationPage/VoteParticipationPage.tsx (1)

95-107: 라벨과 입력 요소를 연결해주세요.

labelhtmlFor="participant-name"을 가리키지만 실제 입력 컴포넌트에 id가 없어 포커스 이동과 보조공학 연동이 깨집니다. ClearableInput에 동일한 id를 전달해 접근성을 복구해 주세요.

-                  <ClearableInput
+                  <ClearableInput
+                    id="participant-name"
                     value={participantName}
                     onChange={(e) => setParticipantName(e.target.value)}
                     onClear={() => setParticipantName('')}
                   />
src/page/DebateVotePage/DebateVotePage.tsx (1)

13-19: 개발 환경에서 QR 링크가 undefined로 생성됩니다.

개발/QA 환경에서는 baseUrlundefined라 QR 값이 undefined/vote/${pollId}로 인코딩되어 투표 진입이 불가능합니다. 환경별로 확실한 기본값을 주고, pollId가 유효하지 않을 때는 빈 문자열을 반환하도록 변경해 주세요.

-  const baseUrl =
-    import.meta.env.MODE !== 'production'
-      ? undefined
-      : import.meta.env.VITE_SHARE_BASE_URL;
-  const voteUrl = useMemo(() => {
-    return `${baseUrl}/vote/${pollId}`;
-  }, [baseUrl, pollId]);
+  const shareBaseUrl =
+    import.meta.env.MODE === 'production'
+      ? import.meta.env.VITE_SHARE_BASE_URL ?? ''
+      : typeof window !== 'undefined'
+        ? window.location.origin
+        : '';
+  const voteUrl = useMemo(() => {
+    if (!isValidPollId) return '';
+    return `${shareBaseUrl}/vote/${pollId}`;
+  }, [shareBaseUrl, pollId, isValidPollId]);
🧹 Nitpick comments (1)
src/type/type.ts (1)

107-127: 팀 스타일링 구성이 잘 정의되었습니다

TeamKeyTimeBasedStance를 재사용하여 이전 리뷰 의견을 잘 반영했습니다. 스타일링 설정도 명확하게 구조화되어 있습니다.

향후 유지보수성 향상을 위해 하드코딩된 색상 값(#C2E8FF, #1E91D6 등)을 별도 상수로 분리하는 것을 고려해볼 수 있습니다. 다만 현재 구현으로도 충분히 동작하므로 선택적으로 적용하면 됩니다.

// 예시: 별도 색상 상수 정의
const TEAM_COLORS = {
  PROS: {
    primary: '#1E91D6',
    bg: '#C2E8FF',
  },
  CONS: {
    primary: '#E14666',
    bg: '#FFC7D3',
  },
} as const;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9258e9 and bd4cb28.

📒 Files selected for processing (10)
  • src/hooks/mutations/useFetchEndpoll.ts (1 hunks)
  • src/page/DebateEndPage/DebateEndPage.stories.tsx (1 hunks)
  • src/page/DebateEndPage/DebateEndPage.tsx (3 hunks)
  • src/page/DebateVotePage/DebateVotePage.tsx (1 hunks)
  • src/page/DebateVoteResultPage/DebateVoteResultPage.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/AnimatedCounter.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/VoteBar.tsx (1 hunks)
  • src/page/DebateVoteResultPage/components/VoteDetailResult.tsx (1 hunks)
  • src/page/VoteParticipationPage/VoteParticipationPage.tsx (1 hunks)
  • src/type/type.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/hooks/mutations/useFetchEndpoll.ts
  • src/page/DebateVoteResultPage/DebateVoteResultPage.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
src/page/DebateVoteResultPage/components/VoteBar.tsx (2)
src/type/type.ts (2)
  • TeamKey (113-113)
  • TEAM_STYLE (114-127)
src/page/DebateVoteResultPage/components/AnimatedCounter.tsx (1)
  • AnimatedCounter (12-31)
src/page/DebateEndPage/DebateEndPage.stories.tsx (1)
src/page/DebateEndPage/DebateEndPage.tsx (1)
  • DebateEndPage (10-70)
src/page/DebateVotePage/DebateVotePage.tsx (3)
src/hooks/query/useGetPollInfo.ts (1)
  • useGetPollInfo (5-15)
src/hooks/mutations/useFetchEndpoll.ts (1)
  • useFetchEndPoll (5-12)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/page/VoteParticipationPage/VoteParticipationPage.tsx (9)
src/type/type.ts (1)
  • TeamKey (113-113)
src/hooks/query/useGetVoterPollInfo.ts (1)
  • useGetVoterPollInfo (5-14)
src/hooks/useModal.tsx (1)
  • useModal (15-87)
src/hooks/mutations/usePostVoterPollInfo.ts (1)
  • usePostVoterPollInfo (5-18)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
src/components/LoadingIndicator/LoadingIndicator.tsx (1)
  • LoadingIndicator (4-13)
src/components/ClearableInput/ClearableInput.tsx (1)
  • ClearableInput (11-37)
src/page/VoteParticipationPage/components/VoteTeamButton.tsx (1)
  • VoteTeamButton (13-57)
src/components/DialogModal/DialogModal.tsx (1)
  • DialogModal (16-67)
src/page/DebateVoteResultPage/components/VoteDetailResult.tsx (1)
src/page/DebateVoteResultPage/components/VoteBar.tsx (1)
  • VoteBar (13-60)
src/page/DebateEndPage/DebateEndPage.tsx (2)
src/hooks/mutations/useCreatePoll.ts (1)
  • usePostPoll (5-12)
src/page/DebateEndPage/components/MenuCard.tsx (1)
  • MenuCard (17-58)
🔇 Additional comments (4)
src/page/DebateEndPage/DebateEndPage.tsx (2)

7-8: LGTM!

usePostPoll 훅과 MenuCard 컴포넌트 import가 적절하게 추가되었습니다.


18-21: LGTM!

handleVoteClick 콜백과 usePostPoll 훅 설정이 올바르게 구현되었습니다. 폴 생성 성공 시 자동으로 투표 페이지로 이동하는 흐름이 명확합니다.

src/page/DebateEndPage/DebateEndPage.stories.tsx (1)

1-25: Storybook 설정이 잘 구성되었습니다.

DebateEndPage의 Storybook 문서화가 적절하게 추가되었습니다. 전체 화면 레이아웃 설정과 기본 스토리 구조가 명확합니다.

src/type/type.ts (1)

80-84: 타입 정의가 명확하고 일관성 있습니다

VoterPollInfoteam 필드가 TeamKey와 일관되게 'PROS' | 'CONS'로 정의되어 있어 타입 안정성이 보장됩니다.

Comment on lines +10 to +11
route: '/table/customize/123/end/vote',
routePattern: '/table/customize/:id/end/vote',
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

라우트 설정이 잘못되었습니다.

현재 route가 /table/customize/123/end/vote로 설정되어 있는데, 이는 투표 결과 페이지로 이동할 때 사용하는 경로입니다. DebateEndPage는 /table/customize/:id/end에 위치해야 합니다.

다음 diff를 적용하세요:

   parameters: {
     layout: 'fullscreen', // Storybook에서 전체 화면으로 표시
-    route: '/table/customize/123/end/vote',
-    routePattern: '/table/customize/:id/end/vote',
+    route: '/table/customize/123/end',
+    routePattern: '/table/customize/:id/end',
   },
📝 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
route: '/table/customize/123/end/vote',
routePattern: '/table/customize/:id/end/vote',
parameters: {
layout: 'fullscreen', // Storybook에서 전체 화면으로 표시
route: '/table/customize/123/end',
routePattern: '/table/customize/:id/end',
},
🤖 Prompt for AI Agents
In src/page/DebateEndPage/DebateEndPage.stories.tsx around lines 10 to 11, the
route and routePattern are incorrect for DebateEndPage; update route from
'/table/customize/123/end/vote' to '/table/customize/123/end' and routePattern
from '/table/customize/:id/end/vote' to '/table/customize/:id/end' so the story
uses the correct end page path.

Comment on lines +68 to +78
export interface BasePollInfo {
status: 'PROGRESS' | 'DONE';
prosTeamName: string;
consTeamName: string;
}
export interface PollInfo extends BasePollInfo {
totalCount: number;
prosCount: number;
consCount: number;
voterNames: string[];
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

투표 집계 데이터의 정합성 검증 확인 필요

PollInfo 타입의 totalCount는 논리적으로 prosCount + consCount와 일치해야 합니다. TypeScript 타입 시스템으로는 이 관계를 강제할 수 없으므로, API 응답을 처리하는 코드나 데이터 핸들러에서 이 정합성을 검증하는 로직이 있는지 확인해 주세요.

다음 스크립트로 PollInfo를 사용하는 코드에서 검증 로직이 있는지 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# PollInfo를 사용하는 위치를 찾고, 카운트 검증 로직이 있는지 확인
rg -nP --type=ts -C5 'PollInfo' | rg -P '(totalCount|prosCount|consCount|validate|check)'

Length of output: 123


총 투표 수 검증 로직 추가 필요
PollInfo.totalCountprosCount + consCount와 일치하는지 API 응답 처리 또는 데이터 핸들러 단계에서 반드시 검증 로직을 구현해주세요.

🤖 Prompt for AI Agents
In src/type/type.ts around lines 68 to 78 the PollInfo model allows totalCount
that may not match prosCount + consCount; add validation in the API response
parsing or data handler that checks totalCount === prosCount + consCount and
handle mismatches (reject the response, throw a descriptive error, or normalize
totals by recomputing totalCount from prosCount + consCount), and ensure any
error includes the offending values and is surfaced to callers/logging so
invalid payloads are rejected or corrected early.

Copy link
Contributor

@useon useon left a comment

Choose a reason for hiding this comment

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

치코 ~! 변경 사항들 확인했습니다 ! 투표 결과 확인하는 부분도 이제 문제 없이 동작하네요 !!!!! 변경 사항들 중에 함수명은 변경됐지만 파일명이 그대로인 부분이 있어 리뷰 남겼으니 한번 확인해 주세요 !!! 동작에는 문제 없어서 일단 바로 어프루브 드립니다 짱짱 !

import { PatchPollResponseType } from '../../apis/responses/poll';
import { usePreventDuplicateMutation } from './usePreventDuplicateMutation';

export default function useFetchEndPoll(onSuccess: (id: number) => void) {
Copy link
Contributor

Choose a reason for hiding this comment

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

치코 함수이름은 useFetchEndPoll로 바뀐 것 확인했습니다! 하지만 파일명은 그대로인 것 같아서 한번 더 확인부탁드려욥 !

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 수정했습니다. git 대소문자 인식 문제 때문에 적용이 되질 않았네요

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 (2)
src/type/type.ts (2)

80-84: 타입 일관성을 위해 TimeBasedStance 재사용 권장

team 필드가 'PROS' | 'CONS' 리터럴 타입을 사용하고 있는데, 이는 이미 정의된 TimeBasedStance와 동일합니다. Line 113의 TeamKeyTimeBasedStance를 재사용하는 것처럼, 여기서도 타입 일관성을 위해 TimeBasedStance를 사용하는 것을 권장합니다.

다음과 같이 수정할 수 있습니다:

 export interface VoterPollInfo {
   name: string;
   participateCode: string;
-  team: 'PROS' | 'CONS';
+  team: TimeBasedStance;
 }

107-127: 색상 값 중앙화로 유지보수성 개선 권장

TEAM_STYLE 상수에 하드코딩된 색상 값(#C2E8FF, #1E91D6 등)이 여러 속성에 중복되어 있습니다. 색상이 변경될 경우 여러 곳을 수정해야 하므로 유지보수가 어려울 수 있습니다.

다음 방안을 고려해보세요:

  1. Tailwind 설정에서 커스텀 색상을 테마로 확장하여 의미있는 이름으로 사용 (예: bg-team-pros, border-team-pros)
  2. 색상 상수를 별도 파일로 분리하여 재사용 (예: src/constants/colors.ts)

예시 - Tailwind 테마 확장:

tailwind.config.js에서:

module.exports = {
  theme: {
    extend: {
      colors: {
        team: {
          pros: {
            bg: '#C2E8FF',
            border: '#1E91D6',
            text: '#1E91D6',
          },
          cons: {
            bg: '#FFC7D3',
            border: '#E14666',
            text: '#E14666',
          },
        },
      },
    },
  },
};

그런 다음 TEAM_STYLE에서:

export const TEAM_STYLE: Record<TeamKey, TeamStyleConfig> = {
  PROS: {
    baseBg: 'bg-team-pros-bg',
    baseBorder: 'border-team-pros-border',
    label: 'text-team-pros-text',
    name: 'text-team-pros-text',
  },
  CONS: {
    baseBg: 'bg-team-cons-bg',
    baseBorder: 'border-team-cons-border',
    label: 'text-team-cons-text',
    name: 'text-team-cons-text',
  },
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 452e103 and 2b6c228.

📒 Files selected for processing (1)
  • src/type/type.ts (2 hunks)
🔇 Additional comments (1)
src/type/type.ts (1)

68-78: totalCount 검증 로직 미발견, 수동 확인 필요

PollInfo를 사용하는 API 응답 처리 레이어에 totalCount === prosCount + consCount 검증 로직이 구현되어 있는지 수동으로 확인해 주세요.

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 (2)
src/page/DebateVotePage/DebateVotePage.tsx (2)

13-19: [중복] QR 코드 URL이 개발환경에서 잘못 생성됩니다

이 이슈는 이전 리뷰에서 이미 상세히 지적되었습니다. 개발 모드에서 baseUrlundefined로 남아 QR에 undefined/vote/{id}가 인코딩되는 문제가 있습니다. 이전 리뷰의 제안대로 수정해 주세요.


110-127: [중복] 투표 종료 버튼 상태 및 오류 처리 구현 필요

이 이슈는 이전 리뷰에서 이미 상세히 지적되었습니다. useFetchEndPoll의 로딩 상태와 에러 처리가 누락되어 있습니다. 이전 리뷰의 제안대로 isPending, isError 등을 destructure하고 버튼에 로딩 상태와 에러 핸들링을 추가해 주세요.

🧹 Nitpick comments (1)
src/page/DebateVotePage/DebateVotePage.tsx (1)

51-61: 버튼 텍스트와 동작 불일치

유효하지 않은 투표 링크일 때 ErrorIndicator의 버튼 텍스트가 "다시 시도하기"로 표시되지만, 실제 동작은 홈으로 이동합니다. 사용자에게 혼란을 줄 수 있습니다.

다음과 같이 버튼 텍스트를 명확하게 변경하는 것을 권장합니다:

-          <ErrorIndicator onClickRetry={() => navigate('/')}>
+          <ErrorIndicator 
+            onClickRetry={() => navigate('/')}
+            retryButtonText="홈으로 돌아가기"
+          >
             유효하지 않은 투표 링크입니다.
           </ErrorIndicator>

또는 ErrorIndicator 컴포넌트가 버튼 텍스트 커스터마이징을 지원하지 않는다면, 별도의 버튼으로 구현하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b6c228 and 0dfed86.

📒 Files selected for processing (2)
  • src/hooks/mutations/useFetchEndPoll.ts (1 hunks)
  • src/page/DebateVotePage/DebateVotePage.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/hooks/mutations/useFetchEndPoll.ts (3)
src/hooks/mutations/usePreventDuplicateMutation.ts (1)
  • usePreventDuplicateMutation (9-52)
src/apis/apis/poll.ts (1)
  • patchEndPoll (57-68)
src/apis/responses/poll.ts (1)
  • PatchPollResponseType (14-16)
src/page/DebateVotePage/DebateVotePage.tsx (3)
src/hooks/query/useGetPollInfo.ts (1)
  • useGetPollInfo (5-15)
src/hooks/mutations/useFetchEndPoll.ts (1)
  • useFetchEndPoll (5-12)
src/components/ErrorIndicator/ErrorIndicator.tsx (1)
  • ErrorIndicator (8-33)
🪛 GitHub Actions: frontend-CI
src/page/DebateVotePage/DebateVotePage.tsx

[error] 7-7: TypeScript error TS1261: Already included file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndPoll.ts' differs from file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndpoll.ts' only in casing.

🪛 GitHub Check: test
src/page/DebateVotePage/DebateVotePage.tsx

[failure] 7-7:
Already included file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndPoll.ts' differs from file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndpoll.ts' only in casing.

🔇 Additional comments (4)
src/hooks/mutations/useFetchEndPoll.ts (2)

1-3: LGTM!

임포트가 명확하고 적절합니다.


5-12: LGTM!

훅 구현이 깔끔하고 타입 안전합니다. 중복 요청 방지 로직도 적절히 적용되었습니다.

src/page/DebateVotePage/DebateVotePage.tsx (2)

9-11: LGTM!

투표 ID 검증 로직이 적절합니다. URL 파라미터의 존재 여부와 숫자 유효성을 모두 확인하고 있습니다.


28-40: LGTM!

데이터 페칭 로직이 잘 구성되어 있습니다. 특히 enabled 옵션으로 유효하지 않은 ID일 때 쿼리를 비활성화하고, 5초 간격 자동 갱신으로 실시간 참여자 목록을 표시하는 것이 좋습니다.

import DefaultLayout from '../../layout/defaultLayout/DefaultLayout';
import { useGetPollInfo } from '../../hooks/query/useGetPollInfo';
import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator';
import useFetchEndPoll from '../../hooks/mutations/useFetchEndPoll';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

파일명 대소문자 불일치로 빌드 실패

임포트 경로의 대소문자가 실제 파일명과 일치하지 않아 case-sensitive 파일시스템(Linux, CI 환경)에서 빌드가 실패합니다.

다음과 같이 수정하세요:

-import useFetchEndPoll from '../../hooks/mutations/useFetchEndpoll';
+import useFetchEndPoll from '../../hooks/mutations/useFetchEndPoll';
📝 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
import useFetchEndPoll from '../../hooks/mutations/useFetchEndPoll';
import useFetchEndPoll from '../../hooks/mutations/useFetchEndPoll';
🧰 Tools
🪛 GitHub Actions: frontend-CI

[error] 7-7: TypeScript error TS1261: Already included file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndPoll.ts' differs from file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndpoll.ts' only in casing.

🪛 GitHub Check: test

[failure] 7-7:
Already included file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndPoll.ts' differs from file name '/home/runner/work/debate-timer-fe/debate-timer-fe/src/hooks/mutations/useFetchEndpoll.ts' only in casing.

🤖 Prompt for AI Agents
In src/page/DebateVotePage/DebateVotePage.tsx around line 7, the import path's
letter casing for the hook does not match the actual filename causing CI/Linux
build failures; update the import to exactly match the real filename in
src/hooks/mutations (adjust the capitalization of the module name to be
identical to the file on disk) so the case-sensitive resolver can locate the
module.

@jaeml06 jaeml06 merged commit 544e99d into develop Oct 12, 2025
1 of 2 checks passed
@jaeml06 jaeml06 deleted the feat/#381 branch October 12, 2025 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEAT] 승패 투표 기능 추가

4 participants