-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] 승패 투표 기능 추가 #387
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] 승패 투표 기능 추가 #387
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough승패 투표 기능을 추가. 폴 API/타입/모킹 추가, 투표 관련 훅(조회/생성/종료/제출) 신설, 라우트 4개 추가. 투표 진행/참여/완료/결과 페이지와 관련 UI 컴포넌트(카운터/바/카드/체크박스) 도입. 일부 스타일 및 푸터 정렬 보정. Changes
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)
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)
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
src/assets/debateEnd/crown.svgis 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!승자 카드 컴포넌트가 잘 구현되었습니다:
nullteamkey 처리가 안전함- 조건부 스타일링이 명확함
- 무승부 시 왕관을 표시하지 않는 로직이 직관적임
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콜백 시그니처 일치
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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
📒 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
주석의 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.
i-meant-to-be
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생 많으셨습니다! 리뷰 요청 사항에서 부탁하신 부분도 의견 남겨두었구요. 제 환경에서는 기능 잘 동작하는 것 같습니다.
리뷰 내용이 좀 많습니다. 또한, 직접 수정을 요청드리는 권장, 대안 수준 외에도 의견과 질문도 섞여 있어서, 세심히 봐 주시면 정말 감사하겠습니다...!
| const TEAM_LABEL = { | ||
| PROS: '찬성팀', | ||
| CONS: '반대팀', | ||
| } as const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
의견: 진영이 반드시 찬성이나 반대로만 나뉘는가?
서비스에 팀 이름을 직접 입력할 수 있게 하는 변경 사항을 도입한 큰 이유 중 하나는, 토론 주제가 찬성 또는 반대로만 나뉘지 않을 가능성도 있기 때문이라고 봅니다. 예를 들어 짜장 vs. 짬뽕, 선별적 복지 vs. 보편적 복지 등의 주제들처럼요. 그러나 이 레이블들은 현재 찬성 팀과 반대 팀으로만 양쪽을 양분하고 있습니다.
다시 말하면, 투표 기능은 모든 주제가 찬반으로 나뉠 거라는 의도 하에 개발되었으나, 테이블 생성 시 팀명은 찬반으로 나뉘지 않을 가능성도 고려하여 개발되었고, 이 두 기능의 의도 사이에 간극이 존재한다는 점입니다.
다만, 이 부분은 단순히 기술적으로 나눌 내용은 아니기 때문에, 향후 회의에서 의견 수렴을 받고 개선하는 방향이 좋아 보입니다. 따라서, 권장이나 대안으로 올리지 않고 단순 의견의 형태로 댓글을 남깁니다. 역시 기억만 해 둡시다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그럼 찬성 반대가 아니라 그냥 팀 이름만 남겨두는 방식으로 두는 것도 좋은 방식이겠네요. 확인했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
📒 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}) 및 실제 라우터 정의와 일치하는지 직접 확인해주세요.
| 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 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
📒 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 클래스를 지원하도록 개선되면 함께 반영하는 것을 고려할 수 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 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
📒 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: 페이지 구조가 명확하고 적절합니다.레이아웃과 스타일링이 투표 완료 페이지의 목적에 잘 맞습니다. 반응형 텍스트 크기 조정도 적절하게 구현되어 있습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/page/DebateVotePage/DebateVotePage.tsx (1)
13-19: QR 코드 URL이 개발 환경에서 여전히 잘못 생성됩니다이전 리뷰에서 지적된 Critical 이슈가 아직 수정되지 않았습니다. 개발 모드(
import.meta.env.MODE !== 'production')에서baseUrl이undefined로 설정되어 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 훅이 올바르게 구현되었고 타입 안정성도 확보되어 있습니다. 몇 가지 선택적 개선사항을 제안드립니다:
- 쿼리 설정 옵션 추가 고려: staleTime, retry 등의 설정을 명시적으로 추가하면 일관성과 예측 가능성이 향상됩니다.
- 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
📒 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: 참여자 목록의 빈 상태 처리가 잘 구현되었습니다로딩 상태와 데이터 존재 여부를 명확하게 구분하여 적절한 메시지를 표시하고 있습니다. 사용자가 현재 상태를 쉽게 이해할 수 있습니다.
| <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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
투표 종료 버튼 동작을 확인하세요
"투표 결과 보기" 버튼을 클릭하면 mutate(pollId)를 호출하여 투표를 종료하고 결과 페이지로 이동합니다. 그러나 다음 사항들을 확인해 주세요:
- 투표가 이미 종료된 상태에서 이 페이지에 접근했을 때의 처리
- 투표 종료 중(
mutate진행 중) 버튼의 비활성화 또는 로딩 표시 - 투표 종료 실패 시 사용자에게 표시할 에러 메시지
현재 코드에서 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 -C3Length of output: 1753
투표 종료 버튼 상태 및 오류 처리 구현 필요
useFetchEndpoll의isLoading상태 동안 “투표 결과 보기” 버튼 비활성화 및 로딩 표시- 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.
i-meant-to-be
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
승인 남깁니다! 리뷰 댓글 양이 많았는데 꼼꼼하게 확인해주셔서 감사해요. 이제 기능 및 코드에서 리뷰를 남길 건 없을 듯하고, 별로 중요하지 않은 코멘트 몇 건만 적어뒀으니 시간 날 때 확인해주시면 감사하겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 것 같아요! 파일 이름만 함께 맞춰주면 될 것 같습니다. 반영 감사합니당!
| heightClass?: string; // h-20 등 높이 조절용 | ||
| }; | ||
|
|
||
| export default function VoteBar({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
대안: 파일 이름과 컴포넌트명 맞추기
파일 이름은 VoteResultRow고 컴포넌트 이름은 VoteBar이네요. 어떤 쪽이든 둘을 맞춰주면 좋을 것 같습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 변경했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
흠 이 부분은 생각보다는 더 엄밀한 고민이 필요하겠네요. 구분법이 꽤 엄격한 편이었네요... 나중에 시간 나면 따져보는 걸로 합시다.
useon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
치코 ~!! 작업량이 많았을텐데 꼼꼼하게 챙겨주셔서 감사합니다 ! 몇가지 코멘트 남겼는데 확인해 주셔요 ~!!!! 특히 제 환경에서는 투표 결과를 확인할 수 없어서 이 부분 확인 부탁드립니다 !!!! ^ _^
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function usePostPoll(onSuccess: (id: number) => void) { | ||
| return usePreventDuplicateMutation({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복호출방지 훅 사용까지 !!! 🥹
| import { PatchPollResponseType } from '../../apis/responses/poll'; | ||
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function useFetchEndpoll(onSuccess: (id: number) => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사소한 것이지만 파일명과 함수명이 useFetchEndpoll 이어서 useFetchEndPoll로 수정되어도 될 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 수정했습니다.
| <DefaultLayout.ContentContainer> | ||
| <div | ||
| style={backgroundStyle} | ||
| className="flex min-h-screen flex-col items-center justify-center p-4 font-pretendard" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DefaultLayout이랑 DefaultLayout.ContentContainer을 추가하셨는데 여기 있는 속성이 div의 속성에도 중복으로 들어가 있는 것 같아요 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
중복을 제거했습니다. 다만 해당 컴포넌트가 페이지의 시작점이기 때문에 Layout이라는 구성을 나타갈 필요가 있다고 판단했습니다. 해당 부분을 Laout으로 분리하는 작업이 필요해 보입니다.
| if (isValidPollId) { | ||
| return ( | ||
| <DefaultLayout> | ||
| <DefaultLayout.ContentContainer> | ||
| <ErrorIndicator onClickRetry={() => navigate('/')}> | ||
| 유효하지 않은 투표 결과 링크입니다. | ||
| </ErrorIndicator> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 여기 유효하지 않은 투표 결과 링크 표시 조건이 pollId가 유효할 때가 맞나요 ??
테스트시 투표는 정상적으로 되고 실시간으로 확인할 수 있었지만 투표 결과 확인하기 누르면 유효하지 않은 링크라고 떠서 찾아보니 이 조건문이 걸려서요 !!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 조건이 !isValidPollId이어야 하는데 실수 했네요. 다시 작성했습니다.
src/type/type.ts
Outdated
| participateCode: string; | ||
| team: 'PROS' | 'CONS'; | ||
| } | ||
| export // ===== 배경 색상 상태 타입 및 컬러 맵 정의 ===== |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
혹시 여기 export 뒤에 선언된 내용이 없는데 의도된 부분일까요 ?!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 지웠다가 실수로 전부 지우지 못했나보네요. 꼼꼼하게 확인 감사합니다.
| }); | ||
|
|
||
| setParticipantName(''); | ||
| setSelectedTeam(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어차피 mutate가 성공하면 vote/end 페이지로 이동하게 되어 실제 화면에 반영되지 않는데 초기화 로직을 작성한 이유가 궁금합니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
굳이 여기서는 필요없어보이긴합니다. 처음에는 api를 연결하지 않아서 작성했던 로직이 남아있는 것입니다. 현재는 불필요하다고 판단되지만 지우겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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: 라벨과 입력 요소를 연결해주세요.
label이htmlFor="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 환경에서는
baseUrl이undefined라 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: 팀 스타일링 구성이 잘 정의되었습니다
TeamKey가TimeBasedStance를 재사용하여 이전 리뷰 의견을 잘 반영했습니다. 스타일링 설정도 명확하게 구조화되어 있습니다.향후 유지보수성 향상을 위해 하드코딩된 색상 값(
#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
📒 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: 타입 정의가 명확하고 일관성 있습니다
VoterPollInfo의team필드가TeamKey와 일관되게'PROS' | 'CONS'로 정의되어 있어 타입 안정성이 보장됩니다.
| route: '/table/customize/123/end/vote', | ||
| routePattern: '/table/customize/:id/end/vote', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
라우트 설정이 잘못되었습니다.
현재 route가 /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.
| 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.
| export interface BasePollInfo { | ||
| status: 'PROGRESS' | 'DONE'; | ||
| prosTeamName: string; | ||
| consTeamName: string; | ||
| } | ||
| export interface PollInfo extends BasePollInfo { | ||
| totalCount: number; | ||
| prosCount: number; | ||
| consCount: number; | ||
| voterNames: string[]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.totalCount가 prosCount + 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.
useon
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
치코 ~! 변경 사항들 확인했습니다 ! 투표 결과 확인하는 부분도 이제 문제 없이 동작하네요 !!!!! 변경 사항들 중에 함수명은 변경됐지만 파일명이 그대로인 부분이 있어 리뷰 남겼으니 한번 확인해 주세요 !!! 동작에는 문제 없어서 일단 바로 어프루브 드립니다 짱짱 !
| import { PatchPollResponseType } from '../../apis/responses/poll'; | ||
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function useFetchEndPoll(onSuccess: (id: number) => void) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
치코 함수이름은 useFetchEndPoll로 바뀐 것 확인했습니다! 하지만 파일명은 그대로인 것 같아서 한번 더 확인부탁드려욥 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 수정했습니다. git 대소문자 인식 문제 때문에 적용이 되질 않았네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/type/type.ts (2)
80-84: 타입 일관성을 위해TimeBasedStance재사용 권장
team필드가'PROS' | 'CONS'리터럴 타입을 사용하고 있는데, 이는 이미 정의된TimeBasedStance와 동일합니다. Line 113의TeamKey가TimeBasedStance를 재사용하는 것처럼, 여기서도 타입 일관성을 위해TimeBasedStance를 사용하는 것을 권장합니다.다음과 같이 수정할 수 있습니다:
export interface VoterPollInfo { name: string; participateCode: string; - team: 'PROS' | 'CONS'; + team: TimeBasedStance; }
107-127: 색상 값 중앙화로 유지보수성 개선 권장
TEAM_STYLE상수에 하드코딩된 색상 값(#C2E8FF,#1E91D6등)이 여러 속성에 중복되어 있습니다. 색상이 변경될 경우 여러 곳을 수정해야 하므로 유지보수가 어려울 수 있습니다.다음 방안을 고려해보세요:
- Tailwind 설정에서 커스텀 색상을 테마로 확장하여 의미있는 이름으로 사용 (예:
bg-team-pros,border-team-pros)- 색상 상수를 별도 파일로 분리하여 재사용 (예:
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
📒 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검증 로직이 구현되어 있는지 수동으로 확인해 주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/page/DebateVotePage/DebateVotePage.tsx (2)
13-19: [중복] QR 코드 URL이 개발환경에서 잘못 생성됩니다이 이슈는 이전 리뷰에서 이미 상세히 지적되었습니다. 개발 모드에서
baseUrl이undefined로 남아 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
📒 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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
파일명 대소문자 불일치로 빌드 실패
임포트 경로의 대소문자가 실제 파일명과 일치하지 않아 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.
| 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.
🚩 연관 이슈
closed #381
📝 작업 내용
승패 투표 기능 추가
승패 투표를 위한 페이지를 구현했습니다.
DebateVotePage
승패 투표 QR과 투표자 명단을 확인할 수 있는 페이지입니다.

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


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


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

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