From 0415e545bcc453d182c2254372987a4fa457b5a5 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sun, 21 Sep 2025 17:27:33 +0900 Subject: [PATCH 01/39] =?UTF-8?q?feat:=20=ED=86=A0=EB=A1=A0=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateEndPage/DebateEndPage.tsx | 125 +++++++------- src/page/DebateVotePage/DebateVotePage.tsx | 180 +++++++++++++++++++++ src/routes/routes.tsx | 6 + 3 files changed, 250 insertions(+), 61 deletions(-) create mode 100644 src/page/DebateVotePage/DebateVotePage.tsx diff --git a/src/page/DebateEndPage/DebateEndPage.tsx b/src/page/DebateEndPage/DebateEndPage.tsx index f631fe5d..d4a47f92 100644 --- a/src/page/DebateEndPage/DebateEndPage.tsx +++ b/src/page/DebateEndPage/DebateEndPage.tsx @@ -4,6 +4,7 @@ import clapImage from '../../assets/debateEnd/clap.png'; import feedbackTimerImage from '../../assets/debateEnd/feedback_timer.png'; import voteStampImage from '../../assets/debateEnd/vote_stamp.png'; import GoToHomeButton from '../../components/GoToHomeButton/GoToHomeButton'; +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; export default function DebateEndPage() { const { id: tableId } = useParams(); @@ -13,75 +14,77 @@ export default function DebateEndPage() { navigate(`/table/customize/${tableId}/end/feedback`); }; + const handleVoteClick = () => { + navigate(`/table/customize/${tableId}/end/vote`); + }; + const backgroundStyle = { background: 'radial-gradient(50% 50% at 50% 50%, #fecd4c21 0%, #ffffff42 100%)', }; return ( -
-
-

- 토론을 모두 마치셨습니다 -

- 박수 -
- -
- {/* 피드백 타이머 카드 */} - +
+

+ 토론을 모두 마치셨습니다 +

+ 박수 +
- {/* 승패투표 카드 */} - + + {/* 승패투표 카드 */} + +
+
+
-

- 빠른 시일 내로 만나 뵙겠습니다. -

- -
-
- -
- + + + ); } diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx new file mode 100644 index 00000000..98e0dbab --- /dev/null +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -0,0 +1,180 @@ +import { useMemo } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { QRCodeSVG } from 'qrcode.react'; + +import LoadingSpinner from '../../components/LoadingSpinner'; + +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +const backgroundStyle = { + background: + 'radial-gradient(80% 80% at 50% 50%, #fecd4c21 0%, #ffffff42 100%)', +}; + +export default function DebateVotePage() { + const { id: tableIdParam } = useParams(); + const tableId = Number(tableIdParam); + const navigate = useNavigate(); + + const defaultBaseUrl = + typeof window !== 'undefined' ? window.location.origin : ''; + const voteBaseUrl = + (import.meta.env.VITE_VOTE_BASE_URL as string | undefined) ?? + defaultBaseUrl; + const voteUrl = useMemo(() => { + if (!tableId || Number.isNaN(tableId)) return defaultBaseUrl; + return `${voteBaseUrl}/vote/${tableId}`; + }, [tableId, voteBaseUrl, defaultBaseUrl]); + + const handleGoToResult = () => { + if (!tableId || Number.isNaN(tableId)) return; + navigate(`/table/customize/${tableId}/end/vote/result`); + }; + const participants = [ + '김토론', + '이찬토론', + '박토론', + '최토론', + '정토론', + '김토론', + '이찬토론', + '박토론', + '최토론', + '정토론', + '최토론', + '정토론', + '김토론', + '이찬토론', + '박토론', + '최토론', + '정토론', + ]; + + const handleGoHome = () => { + navigate('/'); + }; + const isLoading = tableId === undefined || Number.isNaN(tableId); + + return ( + + +
+
+

+ 승패투표 +

+
+ +
+
+

스캔해 주세요!

+
+
+ {isLoading ? ( + + ) : ( + + )} +
+
+
+ + {/*
+
+

+ 참여자 + + ({participants.length}) + +

+
+
+ {isLoading && ( +
+ +
+ )} + {!isLoading && participants.length === 0 && ( +

등록된 토론자가 없어요.

+ )} + {!isLoading && participants.length > 0 && ( +
    + {participants.map((name) => ( +
  • + {name} +
  • + ))} +
+ )} +
+
*/} +
+
+

+ 참여자 + + ({participants.length}) + +

+
+
+ {isLoading && ( +
+ +
+ )} + {!isLoading && participants.length === 0 && ( +

등록된 토론자가 없어요.

+ )} + {!isLoading && participants.length > 0 && ( +
    + {participants.map((name) => ( +
  • + {name} +
  • + ))} +
+ )} +
+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 87e3f1dd..6126ead8 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -13,6 +13,7 @@ import FeedbackTimerPage from '../page/TimerPage/FeedbackTimerPage'; import LandingPage from '../page/LandingPage/LandingPage'; import TableSharingPage from '../page/TableSharingPage/TableSharingPage'; import DebateEndPage from '../page/DebateEndPage/DebateEndPage'; +import DebateVotePage from '../page/DebateVotePage/DebateVotePage'; const routesConfig = [ { @@ -50,6 +51,11 @@ const routesConfig = [ element: , requiresAuth: true, }, + { + path: '/table/customize/:id/end/vote', + element: , + requiresAuth: true, + }, { path: '/oauth', element: , From beca8d10fcb16cce7c4df8ec21f2c05cee816105 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:27:16 +0900 Subject: [PATCH 02/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20api=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/apis/poll.ts | 101 +++++++++++++++++++++++++++++++++++++ src/apis/endpoints.ts | 1 + src/apis/responses/poll.ts | 28 ++++++++++ src/type/type.ts | 21 +++++++- 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/apis/apis/poll.ts create mode 100644 src/apis/responses/poll.ts diff --git a/src/apis/apis/poll.ts b/src/apis/apis/poll.ts new file mode 100644 index 00000000..404a6ade --- /dev/null +++ b/src/apis/apis/poll.ts @@ -0,0 +1,101 @@ +import { VoterPollInfo } from '../../type/type'; +import { ApiUrl } from '../endpoints'; +import { request } from '../primitives'; +import { + GetPollResponseType, + GetVoterPollInfoResponseType, + PatchPollResponseType, + PostCreatePollResponseType, + PostVoterPollInfoResponseType, +} from '../responses/poll'; + +// Template +/* +export async function apiFunc( + +): Promise { + const requestUrl: string = ApiUrl. + const response = await request( + method, + requestUrl, + data, + params, + ); + + return response.data; +} +*/ + +// POST /api/polls/{tableId} +export async function postCreatePoll( + tableId: number, +): Promise { + const requestUrl: string = ApiUrl.poll; + const response = await request( + 'POST', + requestUrl + `/${tableId}`, + null, + null, + ); + + return response.data; +} + +// GET /api/polls/{pollId} +export async function getPollInfo( + pollId: number, +): Promise { + const requestUrl: string = ApiUrl.poll; + const response = await request( + 'GET', + requestUrl + `/${pollId}`, + null, + null, + ); + return response.data; +} + +// PATCH /api/polls/{pollId} +export async function patchEndPoll( + pollId: number, +): Promise { + const requestUrl: string = ApiUrl.poll; + const response = await request( + 'PATCH', + requestUrl + `/${pollId}`, + null, + null, + ); + return response.data; +} + +// GET /api/polls/{pollId}/votes +export async function getVoterPollInfo( + pollId: number, +): Promise { + const requestUrl: string = ApiUrl.poll; + const response = await request( + 'GET', + requestUrl + `/${pollId}/votes`, + null, + null, + ); + + return response.data; +} + +// POST /api/polls/{pollId}/votes +export async function postVoterPollInfo( + pollId: number, + voterInfo: VoterPollInfo, +): Promise { + const requestUrl: string = ApiUrl.poll; + const response = await request( + 'POST', + requestUrl + `/${pollId}/votes`, + voterInfo, + null, + ); + + return response.data; +} diff --git a/src/apis/endpoints.ts b/src/apis/endpoints.ts index 95241a7c..7589d94c 100644 --- a/src/apis/endpoints.ts +++ b/src/apis/endpoints.ts @@ -10,4 +10,5 @@ export const ApiUrl = { table: makeUrl('/table'), parliamentary: makeUrl('/table/parliamentary'), customize: makeUrl('/table/customize'), + poll: makeUrl('/polls'), }; diff --git a/src/apis/responses/poll.ts b/src/apis/responses/poll.ts new file mode 100644 index 00000000..2995cd30 --- /dev/null +++ b/src/apis/responses/poll.ts @@ -0,0 +1,28 @@ +import { CreatePollInfo, PollInfo, VoterPollInfo } from '../../type/type'; + +// POST /api/polls/{tableId} +export interface PostCreatePollResponseType extends CreatePollInfo { + id: number; +} + +// GET /api/polls/{pollId} +export interface GetPollResponseType extends PollInfo { + id: string; +} + +// PATCH /api/polls/{pollId} +export interface PatchPollResponseType extends PollInfo { + id: number; +} + +// GET /api/polls/{pollId}/votes +export interface GetVoterPollInfoResponseType extends PollInfo { + id: number; + participateCode: string; +} + +// POST /api/polls/{pollId}/votes + +export interface PostVoterPollInfoResponseType extends VoterPollInfo { + id: number; +} diff --git a/src/type/type.ts b/src/type/type.ts index b47c0f03..91c3c86a 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -65,8 +65,25 @@ export interface DebateTableData { table: TimeBoxInfo[]; } -// ===== 배경 색상 상태 타입 및 컬러 맵 정의 ===== -export type TimerBGState = 'default' | 'warning' | 'danger' | 'expired'; +export interface CreatePollInfo { + status: 'PROGRESS' | 'DONE'; + prosTeamName: string; + consTeamName: string; +} +export interface PollInfo extends CreatePollInfo { + totalCount: number; + prosCount: number; + consCount: number; + voterNames: string[]; +} + +export interface VoterPollInfo { + name: string; + participateCode: string; + team: 'PROS' | 'CONS'; +} +export // ===== 배경 색상 상태 타입 및 컬러 맵 정의 ===== +type TimerBGState = 'default' | 'warning' | 'danger' | 'expired'; export const bgColorMap: Record = { default: '', warning: 'bg-brand', // 30초~11초 구간 From 702cdd82047aff487036b0a681990a753f4d0c0a Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:28:01 +0900 Subject: [PATCH 03/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20api=20=ED=98=B8=EC=B6=9C=20=EB=A6=AC=EC=95=A1?= =?UTF-8?q?=ED=8A=B8=20=ED=9B=85=20=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mutations/useCreatePoll.ts | 12 ++++++++++++ src/hooks/mutations/useFetchEndpoll.ts | 12 ++++++++++++ src/hooks/mutations/usePostVoterPollInfo.ts | 18 ++++++++++++++++++ src/hooks/query/useGetPollInfo.ts | 14 ++++++++++++++ src/hooks/query/useGetVoterPollInfo.ts | 10 ++++++++++ 5 files changed, 66 insertions(+) create mode 100644 src/hooks/mutations/useCreatePoll.ts create mode 100644 src/hooks/mutations/useFetchEndpoll.ts create mode 100644 src/hooks/mutations/usePostVoterPollInfo.ts create mode 100644 src/hooks/query/useGetPollInfo.ts create mode 100644 src/hooks/query/useGetVoterPollInfo.ts diff --git a/src/hooks/mutations/useCreatePoll.ts b/src/hooks/mutations/useCreatePoll.ts new file mode 100644 index 00000000..d364f72b --- /dev/null +++ b/src/hooks/mutations/useCreatePoll.ts @@ -0,0 +1,12 @@ +import { postCreatePoll } from '../../apis/apis/poll'; +import { PostCreatePollResponseType } from '../../apis/responses/poll'; +import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; + +export default function useCreatePoll(onSuccess: (id: number) => void) { + return usePreventDuplicateMutation({ + mutationFn: (id: number) => postCreatePoll(id), + onSuccess: (response: PostCreatePollResponseType) => { + onSuccess(response.id); + }, + }); +} diff --git a/src/hooks/mutations/useFetchEndpoll.ts b/src/hooks/mutations/useFetchEndpoll.ts new file mode 100644 index 00000000..9d1b4efc --- /dev/null +++ b/src/hooks/mutations/useFetchEndpoll.ts @@ -0,0 +1,12 @@ +import { patchEndPoll } from '../../apis/apis/poll'; +import { PatchPollResponseType } from '../../apis/responses/poll'; +import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; + +export default function useFetchEndpoll(onSuccess: (id: number) => void) { + return usePreventDuplicateMutation({ + mutationFn: (pollId: number) => patchEndPoll(pollId), + onSuccess: (response: PatchPollResponseType) => { + onSuccess(response.id); + }, + }); +} diff --git a/src/hooks/mutations/usePostVoterPollInfo.ts b/src/hooks/mutations/usePostVoterPollInfo.ts new file mode 100644 index 00000000..3e19e5e6 --- /dev/null +++ b/src/hooks/mutations/usePostVoterPollInfo.ts @@ -0,0 +1,18 @@ +import { postVoterPollInfo } from '../../apis/apis/poll'; +import { VoterPollInfo } from '../../type/type'; +import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; + +export default function usePostVoterPollInfo(onSuccess: () => void) { + return usePreventDuplicateMutation({ + mutationFn: ({ + pollId, + voterInfo, + }: { + pollId: number; + voterInfo: VoterPollInfo; + }) => postVoterPollInfo(pollId, voterInfo), + onSuccess: () => { + onSuccess(); + }, + }); +} diff --git a/src/hooks/query/useGetPollInfo.ts b/src/hooks/query/useGetPollInfo.ts new file mode 100644 index 00000000..699c478f --- /dev/null +++ b/src/hooks/query/useGetPollInfo.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; +import { getPollInfo } from '../../apis/apis/poll'; +import { GetPollResponseType } from '../../apis/responses/poll'; + +export function useGetPollInfo( + pollId: number, + options?: { refetchInterval?: number | false }, +) { + return useQuery({ + queryKey: ['Poll', pollId], + queryFn: () => getPollInfo(pollId), + refetchInterval: options?.refetchInterval ?? false, + }); +} diff --git a/src/hooks/query/useGetVoterPollInfo.ts b/src/hooks/query/useGetVoterPollInfo.ts new file mode 100644 index 00000000..29ff1c40 --- /dev/null +++ b/src/hooks/query/useGetVoterPollInfo.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@tanstack/react-query'; +import { getVoterPollInfo } from '../../apis/apis/poll'; +import { GetVoterPollInfoResponseType } from '../../apis/responses/poll'; + +export function useGetVoterPollInfo(pollId: number) { + return useQuery({ + queryKey: ['VoterPoll', pollId], + queryFn: () => getVoterPollInfo(pollId), + }); +} From 4a892fa3580308c466259f450d3cb13de869484f Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:30:20 +0900 Subject: [PATCH 04/39] =?UTF-8?q?feat:=20backgroundImage=EC=97=90=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EB=93=9C=20=EC=83=89=EC=83=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tailwind.config.js b/tailwind.config.js index a32beb13..2c477a6e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -31,6 +31,9 @@ export default { }, }, }, + backgroundImage: { + brandBackground: 'radial-gradient(80% 80% at 50% 50%, #fecd4c21 0%, #ffffff42 100%)', + }, animation: { rotate: 'rotate 5s linear infinite', gradient: 'gradient 10s ease infinite', From cd8cc5867e048abaf60f29ce392c03fbf1bc1b6a Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:30:43 +0900 Subject: [PATCH 05/39] =?UTF-8?q?style:=20footer=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=A0=95=EB=A0=AC=20=EA=B8=B0=EC=A4=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/components/footer/StickyFooterWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layout/components/footer/StickyFooterWrapper.tsx b/src/layout/components/footer/StickyFooterWrapper.tsx index 3a52d507..4fdbb240 100644 --- a/src/layout/components/footer/StickyFooterWrapper.tsx +++ b/src/layout/components/footer/StickyFooterWrapper.tsx @@ -4,7 +4,7 @@ export default function StickyFooterWrapper(props: PropsWithChildren) { const { children } = props; return ( -
+
{children}
); From 2b6518cb88bcec6bc63a25f4403ba37e3829bbe1 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:31:32 +0900 Subject: [PATCH 06/39] =?UTF-8?q?feat:=20=EC=8A=B9=ED=8C=A8=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=20=EC=83=9D=EC=84=B1=20api=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateEndPage/DebateEndPage.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/page/DebateEndPage/DebateEndPage.tsx b/src/page/DebateEndPage/DebateEndPage.tsx index d4a47f92..49a299ff 100644 --- a/src/page/DebateEndPage/DebateEndPage.tsx +++ b/src/page/DebateEndPage/DebateEndPage.tsx @@ -5,6 +5,7 @@ import feedbackTimerImage from '../../assets/debateEnd/feedback_timer.png'; import voteStampImage from '../../assets/debateEnd/vote_stamp.png'; import GoToHomeButton from '../../components/GoToHomeButton/GoToHomeButton'; import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +import useCreatePoll from '../../hooks/mutations/useCreatePoll'; export default function DebateEndPage() { const { id: tableId } = useParams(); @@ -14,10 +15,10 @@ export default function DebateEndPage() { navigate(`/table/customize/${tableId}/end/feedback`); }; - const handleVoteClick = () => { - navigate(`/table/customize/${tableId}/end/vote`); + const handleVoteClick = (pollId: number) => { + navigate(`/table/customize/${pollId}/end/vote`); }; - + const { mutate } = useCreatePoll(handleVoteClick); const backgroundStyle = { background: 'radial-gradient(50% 50% at 50% 50%, #fecd4c21 0%, #ffffff42 100%)', @@ -62,7 +63,7 @@ export default function DebateEndPage() { {/* 승패투표 카드 */} - -
+ +
+ + +
+
From 433b783b38285b1f7830f10aff28bb20703b581f Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:39:29 +0900 Subject: [PATCH 08/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=EC=9E=90=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EC=B0=B8=EC=97=AC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../VoteParticipationPage.tsx | 163 ++++++++++++++++++ .../components/VoteTeamButton.tsx | 57 ++++++ 2 files changed, 220 insertions(+) create mode 100644 src/page/VoteParticipationPage/VoteParticipationPage.tsx create mode 100644 src/page/VoteParticipationPage/components/VoteTeamButton.tsx diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.tsx new file mode 100644 index 00000000..ac68c715 --- /dev/null +++ b/src/page/VoteParticipationPage/VoteParticipationPage.tsx @@ -0,0 +1,163 @@ +import { useState } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import clsx from 'clsx'; +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +import ClearableInput from '../../components/ClearableInput/ClearableInput'; +import { useModal } from '../../hooks/useModal'; +import DialogModal from '../../components/DialogModal/DialogModal'; +import VoteTeamButton from './components/VoteTeamButton'; +import { useGetVoterPollInfo } from '../../hooks/query/useGetVoterPollInfo'; +import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; +import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator'; +import usePostVoterPollInfo from '../../hooks/mutations/usePostVoterPollInfo'; + +const TEAM_LABEL = { + PROS: '찬성팀', + CONS: '반대팀', +} as const; + +type TeamKey = keyof typeof TEAM_LABEL; + +export default function VoteParticipationPage() { + const { id: pollIdParam } = useParams(); + const navigate = useNavigate(); + const pollId = Number(pollIdParam); + + const [participantName, setParticipantName] = useState(''); + const [selectedTeam, setSelectedTeam] = useState(null); + + const { + data, + isLoading: isFetching, + isError: isFetchError, + isRefetching, + refetch, + isRefetchError, + } = useGetVoterPollInfo(pollId); + const { openModal, closeModal, ModalWrapper } = useModal(); + + const isSubmitDisabled = + participantName.trim().length === 0 || selectedTeam === null; + + const { mutate } = usePostVoterPollInfo(() => navigate(`/vote/end`)); + + const handleSubmit = () => { + if (isSubmitDisabled) return; + mutate({ + pollId: pollId, + voterInfo: { + name: participantName.trim(), + participateCode: data?.participateCode ?? '', + team: selectedTeam, + }, + }); + + setParticipantName(''); + setSelectedTeam(null); + }; + + const isLoading = isFetching || isRefetching; + const isError = isFetchError || isRefetchError; + + if (isError) { + return ( + + + refetch()} /> + + + ); + } + + return ( + + + {isLoading && } + {!isLoading && ( +
+
+
+

+ 승패투표 +

+
+ +
+
+ + setParticipantName(e.target.value)} + onClear={() => setParticipantName('')} + /> +
+ +
+ setSelectedTeam('PROS')} + /> + setSelectedTeam('CONS')} + /> +
+
+
+
+ )} + + + + +
+ + closeModal(), + isBold: true, + }} + right={{ + text: '제출하기', + onClick: () => { + handleSubmit(); + closeModal(); + }, + isBold: true, + }} + > +
+

투표를 제출하시겠습니까?

+

(제출 후에는 변경이 불가능 합니다.)

+
+
+
+
+ ); +} diff --git a/src/page/VoteParticipationPage/components/VoteTeamButton.tsx b/src/page/VoteParticipationPage/components/VoteTeamButton.tsx new file mode 100644 index 00000000..62f90236 --- /dev/null +++ b/src/page/VoteParticipationPage/components/VoteTeamButton.tsx @@ -0,0 +1,57 @@ +import clsx from 'clsx'; +import { TEAM_STYLE, TeamKey } from '../../../type/type'; + +interface VoteTeamButtonProps { + label: string; + name: string; + teamkey: TeamKey; + selectedTeam: TeamKey | null; + isSelected: boolean; + onSelect: () => void; +} + +export default function VoteTeamButton({ + label, + name, + teamkey, + isSelected, + selectedTeam, + onSelect, +}: VoteTeamButtonProps) { + const style = TEAM_STYLE[teamkey]; + + return ( + + ); +} From fb7fd3436f740076f6cd7c76d398c6a2fcabc830 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:40:05 +0900 Subject: [PATCH 09/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=EC=9E=90=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20=EC=99=84=EB=A3=8C=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/icons/Check-Box.tsx | 46 +++++++++++++++++++ .../VoteCompletePage/VoteCompletePage.tsx | 21 +++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/components/icons/Check-Box.tsx create mode 100644 src/page/VoteCompletePage/VoteCompletePage.tsx diff --git a/src/components/icons/Check-Box.tsx b/src/components/icons/Check-Box.tsx new file mode 100644 index 00000000..6dbfd0ef --- /dev/null +++ b/src/components/icons/Check-Box.tsx @@ -0,0 +1,46 @@ +import { IconProps } from './IconProps'; + +export default function CheckBox({ + color = 'currentColor', + className = '', + ...props +}: IconProps) { + return ( + + + + + + + + + + + + ); +} diff --git a/src/page/VoteCompletePage/VoteCompletePage.tsx b/src/page/VoteCompletePage/VoteCompletePage.tsx new file mode 100644 index 00000000..2ce2f957 --- /dev/null +++ b/src/page/VoteCompletePage/VoteCompletePage.tsx @@ -0,0 +1,21 @@ +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +import CheckBox from '../../components/icons/Check-Box'; + +export default function VoteCompletePage() { + return ( + + +
+
+ {/* 체크 아이콘 배지 */} + + {/* 완료 메시지 */} +

+ 투표가 완료되었습니다. +

+
+
+
+
+ ); +} From 6351b3774cec1ec8b5c622fd64d5148eb97cdd46 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:44:01 +0900 Subject: [PATCH 10/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/debateEnd/crown.svg | 9 ++ .../DebateVoteResultPage.tsx | 120 ++++++++++++++++++ .../components/CountUp.tsx | 31 +++++ .../components/VoteDetailResult.tsx | 58 +++++++++ .../components/VoteResultRow.tsx | 60 +++++++++ .../components/WinnerCard.tsx | 54 ++++++++ src/type/type.ts | 22 ++++ 7 files changed, 354 insertions(+) create mode 100644 src/assets/debateEnd/crown.svg create mode 100644 src/page/DebateVoteResultPage/DebateVoteResultPage.tsx create mode 100644 src/page/DebateVoteResultPage/components/CountUp.tsx create mode 100644 src/page/DebateVoteResultPage/components/VoteDetailResult.tsx create mode 100644 src/page/DebateVoteResultPage/components/VoteResultRow.tsx create mode 100644 src/page/DebateVoteResultPage/components/WinnerCard.tsx diff --git a/src/assets/debateEnd/crown.svg b/src/assets/debateEnd/crown.svg new file mode 100644 index 00000000..4c356156 --- /dev/null +++ b/src/assets/debateEnd/crown.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx new file mode 100644 index 00000000..e594ebca --- /dev/null +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx @@ -0,0 +1,120 @@ +import { useNavigate, useParams } from 'react-router-dom'; + +import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; +import WinnerCard from './components/WinnerCard'; +import { useModal } from '../../hooks/useModal'; +import VoteDetailResult from './components/VoteDetailResult'; +import { useGetPollInfo } from '../../hooks/query/useGetPollInfo'; +import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; +import { TeamKey } from '../../type/type'; +export default function DebateVoteResultPage() { + const { id: pollIdParam } = useParams(); + const pollId = Number(pollIdParam); + const navigate = useNavigate(); + const { + data, + isLoading: isFetching, + isError: isFetchError, + isRefetching, + refetch, + isRefetchError, + } = useGetPollInfo(pollId); + const handleGoHome = () => { + navigate('/'); + }; + const isLoading = isFetching || isRefetching; + const isError = isFetchError || isRefetchError; + const { openModal, ModalWrapper } = useModal(); + + function getWinner(result: { + prosTeamName: string; + consTeamName: string; + prosCount: number; + consCount: number; + }): { teamKey: TeamKey | null; teamName: string } { + const { prosTeamName, consTeamName, prosCount, consCount } = result; + + if (prosCount > consCount) { + return { + teamKey: 'PROS', + teamName: prosTeamName, + }; + } else if (consCount > prosCount) { + return { + teamKey: 'CONS', + teamName: consTeamName, + }; + } else { + return { + teamKey: null, + teamName: '무승부', + }; + } + } + + if (isError) { + return ( + + + refetch()} /> + + + ); + } + const { teamKey, teamName } = getWinner({ + prosTeamName: data?.prosTeamName || '찬성팀', + consTeamName: data?.consTeamName || '반대팀', + prosCount: data?.prosCount || 0, + consCount: data?.consCount || 0, + }); + return ( + + +
+
+

+ 승패투표 +

+
+ +
+ +
+ + +
+ + +
+
+
+
+ + + +
+ ); +} diff --git a/src/page/DebateVoteResultPage/components/CountUp.tsx b/src/page/DebateVoteResultPage/components/CountUp.tsx new file mode 100644 index 00000000..b0aa25c4 --- /dev/null +++ b/src/page/DebateVoteResultPage/components/CountUp.tsx @@ -0,0 +1,31 @@ +// components/CountUp.tsx +import { useEffect, useState } from 'react'; +import { animate } from 'framer-motion'; + +type CountUpProps = { + to: number; + duration?: number; // 초 + delay?: number; // 초 + className?: string; +}; + +export default function CountUp({ + to, + duration = 1.2, + delay = 0, + className, +}: CountUpProps) { + const [value, setValue] = useState(0); + + useEffect(() => { + const controls = animate(0, to, { + duration, + delay, + ease: 'easeOut', + onUpdate: (latest) => setValue(Math.round(latest)), + }); + return () => controls.stop(); + }, [to, duration, delay]); + + return {value}; +} diff --git a/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx b/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx new file mode 100644 index 00000000..4ad14e9a --- /dev/null +++ b/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx @@ -0,0 +1,58 @@ +// pages/VoteDetailResult.tsx +import { motion } from 'framer-motion'; +import VoteResultRow from './VoteResultRow'; + +type VoteDetailResultProps = { + onGoHome?: () => void; + pros: { name: string; count: number }; + cons: { name: string; count: number }; +}; + +export default function VoteDetailResult({ + onGoHome, + pros, + cons, +}: VoteDetailResultProps) { + return ( +
+ + {/* 내용 */} +
+

+ 투표 세부 결과 +

+ +
+ + +
+
+ + {/* 하단 CTA 바 */} +
+ +
+
+
+ ); +} diff --git a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx new file mode 100644 index 00000000..fda332e6 --- /dev/null +++ b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx @@ -0,0 +1,60 @@ +import { motion } from 'framer-motion'; +import CountUp from './CountUp'; +import { TEAM_STYLE, TeamKey } from '../../../type/type'; + +type VoteBarProps = { + teamKey: TeamKey; // "PROS" | "CONS" + teamName: string; // "단비" / "청춘예찬" + count: number; // 득표 수 + total: number; // 전체 인원 + heightClass?: string; // h-20 등 높이 조절용 +}; + +export default function VoteBar({ + teamKey, + teamName, + count, + total, + heightClass = 'h-20', +}: VoteBarProps) { + const style = TEAM_STYLE[teamKey]; + const percentage = total > 0 ? (count / total) * 100 : 0; + const sideLabel = teamKey === 'PROS' ? '찬성팀' : '반대팀'; + + // 배경 바 색상은 좀 더 투명하게 + const barTone = + teamKey === 'PROS' + ? 'bg-[#C2E8FF]' // 찬성(파랑) + : 'bg-[#FFC7D3]'; // 반대(빨강) + + return ( +
+ {/* 배경 퍼센트바 */} + + + {/* 텍스트 영역 */} +
+
+ + {sideLabel} + + + {teamName} + +
+ +
+ 명 +
+
+
+ ); +} diff --git a/src/page/DebateVoteResultPage/components/WinnerCard.tsx b/src/page/DebateVoteResultPage/components/WinnerCard.tsx new file mode 100644 index 00000000..61c72528 --- /dev/null +++ b/src/page/DebateVoteResultPage/components/WinnerCard.tsx @@ -0,0 +1,54 @@ +import crown from '../../../assets/debateEnd/crown.svg'; +import { TEAM_STYLE, TeamKey } from '../../../type/type'; +import clsx from 'clsx'; + +interface WinnerCardProps { + teamkey: TeamKey | null; // "PROS" | "CONS" | null + teamName: string; // 예: "단비" 또는 "무승부" +} + +export default function WinnerCard({ teamkey, teamName }: WinnerCardProps) { + const style = teamkey ? TEAM_STYLE[teamkey] : null; + const sideLabel = + teamkey === 'PROS' ? '찬성팀' : teamkey === 'CONS' ? '반대팀' : '무승부'; + + return ( +
+ {/* 카드 */} +
+
+

+ {sideLabel} +

+

+ {teamName} +

+
+
+ + {/* 왕관 — 무승부일 때는 표시 안 함 */} + {teamkey && ( +
+ 왕관 +
+ )} +
+ ); +} diff --git a/src/type/type.ts b/src/type/type.ts index 91c3c86a..dd36b7f2 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -103,3 +103,25 @@ export type DebateTemplate = { actions: Action[]; className?: string; // 카드의 추가 className이 필요하면 사용 }; + +type TeamStyleConfig = { + baseBg: string; + baseBorder: string; + label: string; + name: string; +}; +export type TeamKey = 'PROS' | 'CONS'; +export const TEAM_STYLE: Record = { + PROS: { + baseBg: 'bg-[#C2E8FF]', + baseBorder: 'border-[#1E91D6]', + label: 'text-[#1E91D6]', + name: 'text-[#1E91D6]', + }, + CONS: { + baseBg: 'bg-[#FFC7D3]', + baseBorder: 'border-[#E14666]', + label: 'text-[#E14666]', + name: 'text-[#E14666]', + }, +}; From 90302153332e2693d59a8d2ded224f9f2e0b2e4e Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 01:44:33 +0900 Subject: [PATCH 11/39] =?UTF-8?q?feat:=20=ED=88=AC=ED=91=9C=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EA=B5=AC=ED=98=84=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=84=B0=EC=97=90=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/routes.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index 6126ead8..9bf5a5d5 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -14,6 +14,9 @@ import LandingPage from '../page/LandingPage/LandingPage'; import TableSharingPage from '../page/TableSharingPage/TableSharingPage'; import DebateEndPage from '../page/DebateEndPage/DebateEndPage'; import DebateVotePage from '../page/DebateVotePage/DebateVotePage'; +import VoteParticipationPage from '../page/VoteParticipationPage/VoteParticipationPage'; +import VoteCompletePage from '../page/VoteCompletePage/VoteCompletePage'; +import DebateVoteResultPage from '../page/DebateVoteResultPage/DebateVoteResultPage'; const routesConfig = [ { @@ -56,6 +59,21 @@ const routesConfig = [ element: , requiresAuth: true, }, + { + path: '/table/customize/:id/end/vote/result', + element: , + requiresAuth: true, + }, + { + path: '/vote/:id', + element: , + requiresAuth: false, + }, + { + path: '/vote/end', + element: , + requiresAuth: false, + }, { path: '/oauth', element: , From 731d70dda065b68276f9cdcfa1b6d40762924802 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 11:32:01 +0900 Subject: [PATCH 12/39] =?UTF-8?q?test:=20storybook=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/handlers/poll.ts | 92 +++++++++++++++++++ .../DebateVotePage/DebateVotePage.stories.tsx | 23 +++++ .../DebateVoteResultPage.stories.tsx | 23 +++++ .../VoteCompletePage.stories.tsx | 23 +++++ .../VoteParticipationPage.stories.tsx | 23 +++++ 5 files changed, 184 insertions(+) create mode 100644 src/mocks/handlers/poll.ts create mode 100644 src/page/DebateVotePage/DebateVotePage.stories.tsx create mode 100644 src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx create mode 100644 src/page/VoteCompletePage/VoteCompletePage.stories.tsx create mode 100644 src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx diff --git a/src/mocks/handlers/poll.ts b/src/mocks/handlers/poll.ts new file mode 100644 index 00000000..f229a26e --- /dev/null +++ b/src/mocks/handlers/poll.ts @@ -0,0 +1,92 @@ +import { http, HttpResponse } from 'msw'; +import { ApiUrl } from '../../apis/endpoints'; +import { + PatchPollResponseType, + PostCreatePollResponseType, + PostVoterPollInfoResponseType, +} from '../../apis/responses/poll'; + +export const customizeHandlers = [ + // GET /api/table/customize/{tableId} + http.get(ApiUrl.poll + '/:pollId', ({ params }) => { + const { pollId } = params; + console.log(`# pollId = ${pollId}`); + + return HttpResponse.json({ + id: 7, + status: 'PROGRESS', + prosTeamName: '찬성', + consTeamName: '반대', + totalCount: 1, + prosCount: 1, + consCount: 0, + voterNames: ['ㅇㄹㄴ', 'ㅁㄴㅇ'], + }); + }), + + // POST /api/polls/{tableId} + http.post(ApiUrl.poll + '/:tableId', async ({ request }) => { + const result = (await request.json()) as PostCreatePollResponseType; + console.log( + `# tableId = ${result?.id}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, + ); + return HttpResponse.json({ + id: 7, + status: 'PROGRESS', + prosTeamName: '찬성', + consTeamName: '반대', + }); + }), + + // PATCH /api/polls/{pollId} + http.patch(ApiUrl.poll + '/:pollId', async ({ request }) => { + const result = (await request.json()) as PatchPollResponseType; + console.log( + `pollId = ${result?.id}, status = ${result?.status}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, + ); + + return HttpResponse.json({ + id: 7, + status: 'DONE', + prosTeamName: '찬성', + consTeamName: '반대', + totalCount: 1, + prosCount: 1, + consCount: 0, + voterNames: ['ㅇㄹㄴ', 'ㅁㄴㅇ'], + }); + }), + + // GET /api/polls/{pollId}/votes + http.delete(ApiUrl.poll + `/:pollId/votes`, ({ params }) => { + const { pollId } = params; + console.log(`# pollId = ${pollId}`); + + return HttpResponse.json({ + id: 7, + status: 'PROGRESS', + prosTeamName: '찬성', + consTeamName: '반대', + participateCode: '494bcec7-f8e8-4e96-8922-511cd7114a07', + totalCount: 1, + prosCount: 1, + consCount: 0, + }); + }), + + // POST /api/polls/{pollId}/votes + http.patch(ApiUrl.poll + '/:pollId/votes', async ({ request, params }) => { + const { pollId } = params; + const result = (await request.json()) as PostVoterPollInfoResponseType; + console.log( + `# tableId = ${pollId}, name = ${result?.name}, participateCode = ${result?.participateCode}, team = ${result?.team}`, + ); + + return HttpResponse.json({ + id: 7, + name: 'ㅇㄹㄴ', + participateCode: 'string', + team: 'PROS', + }); + }), +]; diff --git a/src/page/DebateVotePage/DebateVotePage.stories.tsx b/src/page/DebateVotePage/DebateVotePage.stories.tsx new file mode 100644 index 00000000..d22c75c4 --- /dev/null +++ b/src/page/DebateVotePage/DebateVotePage.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from '@storybook/react'; +import DebateVotePage from './DebateVotePage'; + +const meta: Meta = { + title: 'page/DebateVotePage', + component: DebateVotePage, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx new file mode 100644 index 00000000..bb0aaa3f --- /dev/null +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from '@storybook/react'; +import DebateVoteResultPage from './DebateVoteResultPage'; + +const meta: Meta = { + title: 'page/DebateVoteResultPage', + component: DebateVoteResultPage, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/page/VoteCompletePage/VoteCompletePage.stories.tsx b/src/page/VoteCompletePage/VoteCompletePage.stories.tsx new file mode 100644 index 00000000..dba4b962 --- /dev/null +++ b/src/page/VoteCompletePage/VoteCompletePage.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from '@storybook/react'; +import VoteCompletePage from './VoteCompletePage'; + +const meta: Meta = { + title: 'page/VoteCompletePage', + component: VoteCompletePage, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx new file mode 100644 index 00000000..48db13f0 --- /dev/null +++ b/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from '@storybook/react'; +import VoteParticipationPage from './VoteParticipationPage'; + +const meta: Meta = { + title: 'page/VoteParticipationPage', + component: VoteParticipationPage, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; From ee9983abe3aa3b2a38158bba51d6a05eb706d711 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 21:03:26 +0900 Subject: [PATCH 13/39] =?UTF-8?q?refactor:=20Create,=20Post=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=82=AC=EC=9A=A9=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/apis/poll.ts | 8 +++----- src/apis/responses/poll.ts | 4 ++-- src/hooks/mutations/useCreatePoll.ts | 10 +++++----- src/mocks/handlers/poll.ts | 4 ++-- src/page/DebateEndPage/DebateEndPage.tsx | 4 ++-- src/type/type.ts | 4 ++-- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/apis/apis/poll.ts b/src/apis/apis/poll.ts index 404a6ade..d66d31dd 100644 --- a/src/apis/apis/poll.ts +++ b/src/apis/apis/poll.ts @@ -5,7 +5,7 @@ import { GetPollResponseType, GetVoterPollInfoResponseType, PatchPollResponseType, - PostCreatePollResponseType, + PostPollResponseType, PostVoterPollInfoResponseType, } from '../responses/poll'; @@ -27,11 +27,9 @@ export async function apiFunc( */ // POST /api/polls/{tableId} -export async function postCreatePoll( - tableId: number, -): Promise { +export async function postPoll(tableId: number): Promise { const requestUrl: string = ApiUrl.poll; - const response = await request( + const response = await request( 'POST', requestUrl + `/${tableId}`, null, diff --git a/src/apis/responses/poll.ts b/src/apis/responses/poll.ts index 2995cd30..9e2ddaa8 100644 --- a/src/apis/responses/poll.ts +++ b/src/apis/responses/poll.ts @@ -1,7 +1,7 @@ -import { CreatePollInfo, PollInfo, VoterPollInfo } from '../../type/type'; +import { BasePollInfo, PollInfo, VoterPollInfo } from '../../type/type'; // POST /api/polls/{tableId} -export interface PostCreatePollResponseType extends CreatePollInfo { +export interface PostPollResponseType extends BasePollInfo { id: number; } diff --git a/src/hooks/mutations/useCreatePoll.ts b/src/hooks/mutations/useCreatePoll.ts index d364f72b..173adbee 100644 --- a/src/hooks/mutations/useCreatePoll.ts +++ b/src/hooks/mutations/useCreatePoll.ts @@ -1,11 +1,11 @@ -import { postCreatePoll } from '../../apis/apis/poll'; -import { PostCreatePollResponseType } from '../../apis/responses/poll'; +import { postPoll } from '../../apis/apis/poll'; +import { PostPollResponseType } from '../../apis/responses/poll'; import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; -export default function useCreatePoll(onSuccess: (id: number) => void) { +export default function usePostPoll(onSuccess: (id: number) => void) { return usePreventDuplicateMutation({ - mutationFn: (id: number) => postCreatePoll(id), - onSuccess: (response: PostCreatePollResponseType) => { + mutationFn: (id: number) => postPoll(id), + onSuccess: (response: PostPollResponseType) => { onSuccess(response.id); }, }); diff --git a/src/mocks/handlers/poll.ts b/src/mocks/handlers/poll.ts index f229a26e..b759d361 100644 --- a/src/mocks/handlers/poll.ts +++ b/src/mocks/handlers/poll.ts @@ -2,7 +2,7 @@ import { http, HttpResponse } from 'msw'; import { ApiUrl } from '../../apis/endpoints'; import { PatchPollResponseType, - PostCreatePollResponseType, + PostPollResponseType, PostVoterPollInfoResponseType, } from '../../apis/responses/poll'; @@ -26,7 +26,7 @@ export const customizeHandlers = [ // POST /api/polls/{tableId} http.post(ApiUrl.poll + '/:tableId', async ({ request }) => { - const result = (await request.json()) as PostCreatePollResponseType; + const result = (await request.json()) as PostPollResponseType; console.log( `# tableId = ${result?.id}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, ); diff --git a/src/page/DebateEndPage/DebateEndPage.tsx b/src/page/DebateEndPage/DebateEndPage.tsx index 49a299ff..cb5c9dfa 100644 --- a/src/page/DebateEndPage/DebateEndPage.tsx +++ b/src/page/DebateEndPage/DebateEndPage.tsx @@ -5,7 +5,7 @@ import feedbackTimerImage from '../../assets/debateEnd/feedback_timer.png'; import voteStampImage from '../../assets/debateEnd/vote_stamp.png'; import GoToHomeButton from '../../components/GoToHomeButton/GoToHomeButton'; import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; -import useCreatePoll from '../../hooks/mutations/useCreatePoll'; +import usePostPoll from '../../hooks/mutations/useCreatePoll'; export default function DebateEndPage() { const { id: tableId } = useParams(); @@ -18,7 +18,7 @@ export default function DebateEndPage() { const handleVoteClick = (pollId: number) => { navigate(`/table/customize/${pollId}/end/vote`); }; - const { mutate } = useCreatePoll(handleVoteClick); + const { mutate } = usePostPoll(handleVoteClick); const backgroundStyle = { background: 'radial-gradient(50% 50% at 50% 50%, #fecd4c21 0%, #ffffff42 100%)', diff --git a/src/type/type.ts b/src/type/type.ts index dd36b7f2..1114db84 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -65,12 +65,12 @@ export interface DebateTableData { table: TimeBoxInfo[]; } -export interface CreatePollInfo { +export interface BasePollInfo { status: 'PROGRESS' | 'DONE'; prosTeamName: string; consTeamName: string; } -export interface PollInfo extends CreatePollInfo { +export interface PollInfo extends BasePollInfo { totalCount: number; prosCount: number; consCount: number; From fed2a0e53716b8603be39dec3d229d730f236492 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 21:17:37 +0900 Subject: [PATCH 14/39] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=EB=AA=85?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/icons/{Check-Box.tsx => CheckBox.tsx} | 0 src/page/VoteCompletePage/VoteCompletePage.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/components/icons/{Check-Box.tsx => CheckBox.tsx} (100%) diff --git a/src/components/icons/Check-Box.tsx b/src/components/icons/CheckBox.tsx similarity index 100% rename from src/components/icons/Check-Box.tsx rename to src/components/icons/CheckBox.tsx diff --git a/src/page/VoteCompletePage/VoteCompletePage.tsx b/src/page/VoteCompletePage/VoteCompletePage.tsx index 2ce2f957..89f3e390 100644 --- a/src/page/VoteCompletePage/VoteCompletePage.tsx +++ b/src/page/VoteCompletePage/VoteCompletePage.tsx @@ -1,5 +1,5 @@ import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; -import CheckBox from '../../components/icons/Check-Box'; +import CheckBox from '../../components/icons/CheckBox'; export default function VoteCompletePage() { return ( From 662bff0eba3a5240b911c4a1afa89c67233470df Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Tue, 7 Oct 2025 21:18:02 +0900 Subject: [PATCH 15/39] =?UTF-8?q?refactor:=20GetPollResponseType=EC=9D=84?= =?UTF-8?q?=20=ED=83=80=EC=9E=85=EC=9D=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/apis/responses/poll.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apis/responses/poll.ts b/src/apis/responses/poll.ts index 9e2ddaa8..01295050 100644 --- a/src/apis/responses/poll.ts +++ b/src/apis/responses/poll.ts @@ -7,7 +7,7 @@ export interface PostPollResponseType extends BasePollInfo { // GET /api/polls/{pollId} export interface GetPollResponseType extends PollInfo { - id: string; + id: number; } // PATCH /api/polls/{pollId} From 66066e563249334b37356920ee20bfdce5910444 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 13:13:14 +0900 Subject: [PATCH 16/39] =?UTF-8?q?refactor:=20CheckBox=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/icons/CheckBox.tsx | 69 ++++++++----------- .../VoteCompletePage/VoteCompletePage.tsx | 2 +- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/components/icons/CheckBox.tsx b/src/components/icons/CheckBox.tsx index 6dbfd0ef..20c7438b 100644 --- a/src/components/icons/CheckBox.tsx +++ b/src/components/icons/CheckBox.tsx @@ -1,46 +1,35 @@ -import { IconProps } from './IconProps'; +import DTCheck from './Check'; +import clsx from 'clsx'; + +interface CheckBoxProps { + color?: string; // 배경색 + checkColor?: string; // 체크 표시 색상 + checked?: boolean; + size?: number | string; + className?: string; +} export default function CheckBox({ - color = 'currentColor', - className = '', - ...props -}: IconProps) { + color = '#FECD4C', + checkColor = '#FFFFFF', + checked = false, + size = 24, + className, +}: CheckBoxProps) { return ( - - - - - - - - - - - + {checked && } + ); } diff --git a/src/page/VoteCompletePage/VoteCompletePage.tsx b/src/page/VoteCompletePage/VoteCompletePage.tsx index 89f3e390..2b01261f 100644 --- a/src/page/VoteCompletePage/VoteCompletePage.tsx +++ b/src/page/VoteCompletePage/VoteCompletePage.tsx @@ -8,7 +8,7 @@ export default function VoteCompletePage() {
{/* 체크 아이콘 배지 */} - + {/* 완료 메시지 */}

투표가 완료되었습니다. From ca7c0483fac20727d1c8d0533ba426823ca3ff82 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 13:38:14 +0900 Subject: [PATCH 17/39] =?UTF-8?q?fix:=20pollId=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DebateVoteResultPage.tsx | 14 +++++++++++++- .../VoteParticipationPage.tsx | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx index e594ebca..83fa31be 100644 --- a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx @@ -9,8 +9,9 @@ import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; import { TeamKey } from '../../type/type'; export default function DebateVoteResultPage() { const { id: pollIdParam } = useParams(); - const pollId = Number(pollIdParam); + const pollId = pollIdParam ? Number(pollIdParam) : NaN; const navigate = useNavigate(); + const { data, isLoading: isFetching, @@ -52,6 +53,17 @@ export default function DebateVoteResultPage() { } } + if (!pollIdParam || Number.isNaN(pollId)) { + return ( + + + navigate('/')}> + 유효하지 않은 투표 결과 링크입니다. + + + + ); + } if (isError) { return ( diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.tsx index ac68c715..67c68e88 100644 --- a/src/page/VoteParticipationPage/VoteParticipationPage.tsx +++ b/src/page/VoteParticipationPage/VoteParticipationPage.tsx @@ -10,18 +10,17 @@ import { useGetVoterPollInfo } from '../../hooks/query/useGetVoterPollInfo'; import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator'; import usePostVoterPollInfo from '../../hooks/mutations/usePostVoterPollInfo'; +import { TeamKey } from '../../type/type'; const TEAM_LABEL = { PROS: '찬성팀', CONS: '반대팀', } as const; -type TeamKey = keyof typeof TEAM_LABEL; - export default function VoteParticipationPage() { const { id: pollIdParam } = useParams(); const navigate = useNavigate(); - const pollId = Number(pollIdParam); + const pollId = pollIdParam ? Number(pollIdParam) : NaN; const [participantName, setParticipantName] = useState(''); const [selectedTeam, setSelectedTeam] = useState(null); @@ -69,6 +68,18 @@ export default function VoteParticipationPage() { ); } + if (!pollIdParam || Number.isNaN(pollId)) { + return ( + + + navigate('/')}> + 유효하지 않은 투표 링크입니다. + + + + ); + } + return ( From 314facb70063576f329c2e2762eea6db040a10f6 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 13:42:07 +0900 Subject: [PATCH 18/39] =?UTF-8?q?fix:=20msw=20=EC=97=B0=EA=B4=80=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=9F=AC=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/handlers/global.ts | 7 ++++++- src/mocks/handlers/poll.ts | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mocks/handlers/global.ts b/src/mocks/handlers/global.ts index 89ffcf57..f43ded4d 100644 --- a/src/mocks/handlers/global.ts +++ b/src/mocks/handlers/global.ts @@ -1,4 +1,9 @@ import { customizeHandlers } from './customize'; import { memberHandlers } from './member'; +import { pollHandlers } from './poll'; -export const allHandlers = [...memberHandlers, ...customizeHandlers]; +export const allHandlers = [ + ...memberHandlers, + ...customizeHandlers, + ...pollHandlers, +]; diff --git a/src/mocks/handlers/poll.ts b/src/mocks/handlers/poll.ts index b759d361..07b90b8d 100644 --- a/src/mocks/handlers/poll.ts +++ b/src/mocks/handlers/poll.ts @@ -6,8 +6,8 @@ import { PostVoterPollInfoResponseType, } from '../../apis/responses/poll'; -export const customizeHandlers = [ - // GET /api/table/customize/{tableId} +export const pollHandlers = [ + // GET /api/polls/:pollId http.get(ApiUrl.poll + '/:pollId', ({ params }) => { const { pollId } = params; console.log(`# pollId = ${pollId}`); From cf7cf7a99d30111ca34acb7dbc6efee67b4caed1 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:03:05 +0900 Subject: [PATCH 19/39] =?UTF-8?q?test:=20storybook=EC=9D=B4=20url=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20param=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVotePage/DebateVotePage.stories.tsx | 2 ++ .../DebateVoteResultPage.stories.tsx | 2 ++ .../VoteParticipationPage.stories.tsx | 9 +++++---- src/page/VoteParticipationPage/VoteParticipationPage.tsx | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/page/DebateVotePage/DebateVotePage.stories.tsx b/src/page/DebateVotePage/DebateVotePage.stories.tsx index d22c75c4..cd29aada 100644 --- a/src/page/DebateVotePage/DebateVotePage.stories.tsx +++ b/src/page/DebateVotePage/DebateVotePage.stories.tsx @@ -7,6 +7,8 @@ const meta: Meta = { tags: ['autodocs'], parameters: { layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + route: '/table/customize/123/end/vote', + routePattern: '/table/customize/:id/end/vote', }, }; diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx index bb0aaa3f..d4bf298c 100644 --- a/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.stories.tsx @@ -7,6 +7,8 @@ const meta: Meta = { tags: ['autodocs'], parameters: { layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + route: '/table/customize/123/end/vote/result', + routePattern: '/table/customize/:id/end/vote/result', }, }; diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx index 48db13f0..bb08e784 100644 --- a/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx +++ b/src/page/VoteParticipationPage/VoteParticipationPage.stories.tsx @@ -1,22 +1,23 @@ +// VoteCompletePage.stories.tsx import { Meta, StoryObj } from '@storybook/react'; import VoteParticipationPage from './VoteParticipationPage'; const meta: Meta = { title: 'page/VoteParticipationPage', component: VoteParticipationPage, - tags: ['autodocs'], parameters: { - layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + layout: 'fullscreen', + route: '/vote/123', + routePattern: '/vote/:id', }, }; export default meta; - type Story = StoryObj; export const Default: Story = { render: () => ( -
+
), diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.tsx index 67c68e88..92e2b4e4 100644 --- a/src/page/VoteParticipationPage/VoteParticipationPage.tsx +++ b/src/page/VoteParticipationPage/VoteParticipationPage.tsx @@ -20,6 +20,7 @@ const TEAM_LABEL = { export default function VoteParticipationPage() { const { id: pollIdParam } = useParams(); const navigate = useNavigate(); + console.log('pollIdParam', pollIdParam); const pollId = pollIdParam ? Number(pollIdParam) : NaN; const [participantName, setParticipantName] = useState(''); From d5e72029909e62c03b7238ee8e2679f66c6a3d56 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:03:31 +0900 Subject: [PATCH 20/39] =?UTF-8?q?fix:=20msw=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=A5=BC=20=EC=98=AC=EB=B0=94=EB=A5=B4=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/handlers/poll.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/mocks/handlers/poll.ts b/src/mocks/handlers/poll.ts index 07b90b8d..b7b9b457 100644 --- a/src/mocks/handlers/poll.ts +++ b/src/mocks/handlers/poll.ts @@ -28,7 +28,7 @@ export const pollHandlers = [ http.post(ApiUrl.poll + '/:tableId', async ({ request }) => { const result = (await request.json()) as PostPollResponseType; console.log( - `# tableId = ${result?.id}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, + `# pollId = ${result?.id}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, ); return HttpResponse.json({ id: 7, @@ -39,10 +39,11 @@ export const pollHandlers = [ }), // PATCH /api/polls/{pollId} - http.patch(ApiUrl.poll + '/:pollId', async ({ request }) => { + http.patch(ApiUrl.poll + '/:pollId', async ({ request, params }) => { const result = (await request.json()) as PatchPollResponseType; + const { pollId } = params; console.log( - `pollId = ${result?.id}, status = ${result?.status}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, + `pollId = ${pollId}, status = ${result?.status}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, ); return HttpResponse.json({ @@ -58,7 +59,7 @@ export const pollHandlers = [ }), // GET /api/polls/{pollId}/votes - http.delete(ApiUrl.poll + `/:pollId/votes`, ({ params }) => { + http.get(ApiUrl.poll + `/:pollId/votes`, ({ params }) => { const { pollId } = params; console.log(`# pollId = ${pollId}`); @@ -75,11 +76,11 @@ export const pollHandlers = [ }), // POST /api/polls/{pollId}/votes - http.patch(ApiUrl.poll + '/:pollId/votes', async ({ request, params }) => { + http.post(ApiUrl.poll + '/:pollId/votes', async ({ request, params }) => { const { pollId } = params; const result = (await request.json()) as PostVoterPollInfoResponseType; console.log( - `# tableId = ${pollId}, name = ${result?.name}, participateCode = ${result?.participateCode}, team = ${result?.team}`, + `# pollId = ${pollId}, name = ${result?.name}, participateCode = ${result?.participateCode}, team = ${result?.team}`, ); return HttpResponse.json({ From 3a52de5568cfa896ce787fcade3a95ee06dbc32d Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:28:07 +0900 Subject: [PATCH 21/39] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=82=AC=ED=95=9C?= =?UTF-8?q?=20=EB=94=94=EC=9E=90=EC=9D=B8=20=EB=B2=84=ED=8A=BC=EC=9D=84=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateEndPage/DebateEndPage.tsx | 54 ++++++----------- .../DebateEndPage/components/MenuCard.tsx | 58 +++++++++++++++++++ 2 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 src/page/DebateEndPage/components/MenuCard.tsx diff --git a/src/page/DebateEndPage/DebateEndPage.tsx b/src/page/DebateEndPage/DebateEndPage.tsx index cb5c9dfa..c78066e7 100644 --- a/src/page/DebateEndPage/DebateEndPage.tsx +++ b/src/page/DebateEndPage/DebateEndPage.tsx @@ -6,6 +6,7 @@ import voteStampImage from '../../assets/debateEnd/vote_stamp.png'; import GoToHomeButton from '../../components/GoToHomeButton/GoToHomeButton'; import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; import usePostPoll from '../../hooks/mutations/useCreatePoll'; +import MenuCard from './components/MenuCard'; export default function DebateEndPage() { const { id: tableId } = useParams(); @@ -43,43 +44,26 @@ export default function DebateEndPage() {
- {/* 피드백 타이머 카드 */} - + ariaLabel="피드백 타이머로 이동" + /> - {/* 승패투표 카드 */} - + { + if (!tableId) return; // NaN 방지 + mutate(Number(tableId)); + }} + ariaLabel="승패투표 생성 및 진행" + />
diff --git a/src/page/DebateEndPage/components/MenuCard.tsx b/src/page/DebateEndPage/components/MenuCard.tsx new file mode 100644 index 00000000..25ab9995 --- /dev/null +++ b/src/page/DebateEndPage/components/MenuCard.tsx @@ -0,0 +1,58 @@ +// src/components/MenuCard/MenuCard.tsx +import clsx from 'clsx'; + +type MenuCardProps = { + title: string; + description?: string; + imgSrc: string; + imgAlt?: string; + onClick?: () => void; + className?: string; + ariaLabel?: string; +}; + +const titleSize = 'text-lg md:text-xl lg:text-2xl xl:text-title-raw'; +const descSize = 'text-sm md:text-base lg:text-lg xl:text-detail-raw'; + +export default function MenuCard({ + title, + description, + imgSrc, + imgAlt = '', + onClick, + className, + ariaLabel, +}: MenuCardProps) { + return ( + + ); +} From 3c7469698bbe2926dd76dddd196b09a6f4a44c26 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:28:22 +0900 Subject: [PATCH 22/39] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVotePage/DebateVotePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx index 6cb4c9d1..ad84e866 100644 --- a/src/page/DebateVotePage/DebateVotePage.tsx +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -18,7 +18,6 @@ export default function DebateVotePage() { }, [baseUrl, pollId]); const handleGoToResult = () => { - if (!pollId || Number.isNaN(pollId)) return; navigate(`/table/customize/${pollId}/end/vote/result`); }; From 1062c7c92729933da57e04f8ba0c87de4aecd152 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:38:54 +0900 Subject: [PATCH 23/39] =?UTF-8?q?style:=20=EC=95=A0=EB=8B=88=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=8B=9C=EA=B0=84=20=EA=B0=90=EC=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVoteResultPage/components/VoteResultRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx index fda332e6..d20b0ad4 100644 --- a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx +++ b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx @@ -36,7 +36,7 @@ export default function VoteBar({ className={`absolute inset-y-0 left-0 ${barTone} rounded-r-2xl`} initial={{ width: '0%' }} animate={{ width: `${percentage}%` }} - transition={{ duration: 1.2, ease: 'easeOut' }} + transition={{ duration: 0.8, ease: 'easeOut' }} aria-hidden /> From e6d8b9ce02e3f545edd1f7a186884548d2196423 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:41:10 +0900 Subject: [PATCH 24/39] =?UTF-8?q?refactor:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVoteResultPage/components/CountUp.tsx | 6 +++--- src/page/DebateVoteResultPage/components/VoteResultRow.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/page/DebateVoteResultPage/components/CountUp.tsx b/src/page/DebateVoteResultPage/components/CountUp.tsx index b0aa25c4..ec0c5929 100644 --- a/src/page/DebateVoteResultPage/components/CountUp.tsx +++ b/src/page/DebateVoteResultPage/components/CountUp.tsx @@ -2,19 +2,19 @@ import { useEffect, useState } from 'react'; import { animate } from 'framer-motion'; -type CountUpProps = { +type AnimatedCounterProps = { to: number; duration?: number; // 초 delay?: number; // 초 className?: string; }; -export default function CountUp({ +export default function AnimatedCounter({ to, duration = 1.2, delay = 0, className, -}: CountUpProps) { +}: AnimatedCounterProps) { const [value, setValue] = useState(0); useEffect(() => { diff --git a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx index d20b0ad4..9797afe5 100644 --- a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx +++ b/src/page/DebateVoteResultPage/components/VoteResultRow.tsx @@ -1,5 +1,5 @@ import { motion } from 'framer-motion'; -import CountUp from './CountUp'; +import AnimatedCounter from './CountUp'; import { TEAM_STYLE, TeamKey } from '../../../type/type'; type VoteBarProps = { @@ -52,7 +52,7 @@ export default function VoteBar({
- 명 +

From 744da7bb9876a0a01d4f096a47bb1e47deb2334b Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:46:18 +0900 Subject: [PATCH 25/39] =?UTF-8?q?refactor:=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=9E=AC=ED=99=9C=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/type.ts b/src/type/type.ts index 1114db84..1a24b4ed 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -110,7 +110,7 @@ type TeamStyleConfig = { label: string; name: string; }; -export type TeamKey = 'PROS' | 'CONS'; +export type TeamKey = TimeBasedStance; export const TEAM_STYLE: Record = { PROS: { baseBg: 'bg-[#C2E8FF]', From 50437c151f800129f2927ace3f248401bc0d0a75 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 14:46:44 +0900 Subject: [PATCH 26/39] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=EC=8B=9D=EC=9D=84=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=ED=91=9C=ED=98=84=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVoteResultPage/DebateVoteResultPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx index 83fa31be..e82d8aa7 100644 --- a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx @@ -27,12 +27,12 @@ export default function DebateVoteResultPage() { const isError = isFetchError || isRefetchError; const { openModal, ModalWrapper } = useModal(); - function getWinner(result: { + const getWinner = (result: { prosTeamName: string; consTeamName: string; prosCount: number; consCount: number; - }): { teamKey: TeamKey | null; teamName: string } { + }): { teamKey: TeamKey | null; teamName: string } => { const { prosTeamName, consTeamName, prosCount, consCount } = result; if (prosCount > consCount) { @@ -51,7 +51,7 @@ export default function DebateVoteResultPage() { teamName: '무승부', }; } - } + }; if (!pollIdParam || Number.isNaN(pollId)) { return ( From a33ce256afd90e9cb635e39d915315a0949c72fc Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 15:07:55 +0900 Subject: [PATCH 27/39] =?UTF-8?q?refactor:=20=EC=83=89=EC=83=81=EC=9D=84?= =?UTF-8?q?=20=EC=A7=80=EC=A0=95=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/icons/CheckBox.tsx | 3 --- src/page/VoteCompletePage/VoteCompletePage.tsx | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/icons/CheckBox.tsx b/src/components/icons/CheckBox.tsx index 20c7438b..428d3d50 100644 --- a/src/components/icons/CheckBox.tsx +++ b/src/components/icons/CheckBox.tsx @@ -2,7 +2,6 @@ import DTCheck from './Check'; import clsx from 'clsx'; interface CheckBoxProps { - color?: string; // 배경색 checkColor?: string; // 체크 표시 색상 checked?: boolean; size?: number | string; @@ -10,7 +9,6 @@ interface CheckBoxProps { } export default function CheckBox({ - color = '#FECD4C', checkColor = '#FFFFFF', checked = false, size = 24, @@ -24,7 +22,6 @@ export default function CheckBox({ className, )} style={{ - backgroundColor: checked ? color : 'transparent', width: size, height: size, }} diff --git a/src/page/VoteCompletePage/VoteCompletePage.tsx b/src/page/VoteCompletePage/VoteCompletePage.tsx index 2b01261f..209ca11f 100644 --- a/src/page/VoteCompletePage/VoteCompletePage.tsx +++ b/src/page/VoteCompletePage/VoteCompletePage.tsx @@ -8,7 +8,7 @@ export default function VoteCompletePage() {
{/* 체크 아이콘 배지 */} - + {/* 완료 메시지 */}

투표가 완료되었습니다. From 6211fdf71e137866d1ad0198a98c7f8154267011 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 15:12:37 +0900 Subject: [PATCH 28/39] =?UTF-8?q?refactor:=20=EC=83=89=EC=83=81=EC=9D=84?= =?UTF-8?q?=20=EC=A7=80=EC=A0=95=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/icons/CheckBox.tsx | 13 +++++++++---- src/page/VoteCompletePage/VoteCompletePage.tsx | 6 +++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/components/icons/CheckBox.tsx b/src/components/icons/CheckBox.tsx index 428d3d50..14fb5712 100644 --- a/src/components/icons/CheckBox.tsx +++ b/src/components/icons/CheckBox.tsx @@ -2,18 +2,23 @@ import DTCheck from './Check'; import clsx from 'clsx'; interface CheckBoxProps { - checkColor?: string; // 체크 표시 색상 checked?: boolean; size?: number | string; className?: string; } +/** + *text-* 클래스가 포함되면 자동으로 DTCheck로 전달 + */ export default function CheckBox({ - checkColor = '#FFFFFF', checked = false, size = 24, - className, + className = '', }: CheckBoxProps) { + // text-로 시작하는 클래스만 추출 + const textClass = + className.split(' ').find((c) => c.startsWith('text-')) ?? ''; + return (
- {checked && } + {checked && }
); } diff --git a/src/page/VoteCompletePage/VoteCompletePage.tsx b/src/page/VoteCompletePage/VoteCompletePage.tsx index 209ca11f..8e5ddcf1 100644 --- a/src/page/VoteCompletePage/VoteCompletePage.tsx +++ b/src/page/VoteCompletePage/VoteCompletePage.tsx @@ -8,7 +8,11 @@ export default function VoteCompletePage() {
{/* 체크 아이콘 배지 */} - + {/* 완료 메시지 */}

투표가 완료되었습니다. From d9258e9da4b40f23d1ffd48f098f56eae2adcb3f Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Wed, 8 Oct 2025 15:24:00 +0900 Subject: [PATCH 29/39] =?UTF-8?q?fix:=20pollId=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=EC=9D=98=20=EB=90=98=EC=96=B4=EC=95=BC=20api=ED=9B=85=EC=9D=B4?= =?UTF-8?q?=20=ED=98=B8=EC=B6=9C=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/query/useGetPollInfo.ts | 3 ++- src/hooks/query/useGetVoterPollInfo.ts | 6 +++++- src/page/DebateVotePage/DebateVotePage.tsx | 17 ++++++++++++++--- .../DebateVoteResultPage.tsx | 6 ++++-- .../VoteParticipationPage.tsx | 7 ++++--- 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/hooks/query/useGetPollInfo.ts b/src/hooks/query/useGetPollInfo.ts index 699c478f..ab5948c1 100644 --- a/src/hooks/query/useGetPollInfo.ts +++ b/src/hooks/query/useGetPollInfo.ts @@ -4,11 +4,12 @@ import { GetPollResponseType } from '../../apis/responses/poll'; export function useGetPollInfo( pollId: number, - options?: { refetchInterval?: number | false }, + options?: { refetchInterval?: number | false; enabled?: boolean }, ) { return useQuery({ queryKey: ['Poll', pollId], queryFn: () => getPollInfo(pollId), refetchInterval: options?.refetchInterval ?? false, + enabled: options?.enabled, }); } diff --git a/src/hooks/query/useGetVoterPollInfo.ts b/src/hooks/query/useGetVoterPollInfo.ts index 29ff1c40..d10efbd5 100644 --- a/src/hooks/query/useGetVoterPollInfo.ts +++ b/src/hooks/query/useGetVoterPollInfo.ts @@ -2,9 +2,13 @@ import { useQuery } from '@tanstack/react-query'; import { getVoterPollInfo } from '../../apis/apis/poll'; import { GetVoterPollInfoResponseType } from '../../apis/responses/poll'; -export function useGetVoterPollInfo(pollId: number) { +export function useGetVoterPollInfo( + pollId: number, + options?: { enabled?: boolean }, +) { return useQuery({ queryKey: ['VoterPoll', pollId], queryFn: () => getVoterPollInfo(pollId), + enabled: options?.enabled, }); } diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx index ad84e866..55db5a26 100644 --- a/src/page/DebateVotePage/DebateVotePage.tsx +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -7,7 +7,8 @@ import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; import useFetchEndpoll from '../../hooks/mutations/useFetchEndpoll'; export default function DebateVotePage() { const { id: pollIdParam } = useParams(); - const pollId = Number(pollIdParam); + const pollId = pollIdParam ? Number(pollIdParam) : NaN; + const isValidPollId = !!pollIdParam && !Number.isNaN(pollId); const navigate = useNavigate(); const baseUrl = import.meta.env.MODE !== 'production' @@ -31,7 +32,7 @@ export default function DebateVotePage() { isRefetching, refetch, isRefetchError, - } = useGetPollInfo(pollId, { refetchInterval: 5000 }); + } = useGetPollInfo(pollId, { refetchInterval: 5000, enabled: isValidPollId }); const { mutate } = useFetchEndpoll(handleGoToResult); const participants = data?.voterNames; @@ -47,7 +48,17 @@ export default function DebateVotePage() { ); } - + if (!isValidPollId) { + return ( + + + navigate('/')}> + 유효하지 않은 투표 링크입니다. + + + + ); + } return ( diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx index e82d8aa7..9014b926 100644 --- a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx @@ -9,7 +9,9 @@ import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; import { TeamKey } from '../../type/type'; export default function DebateVoteResultPage() { const { id: pollIdParam } = useParams(); + const pollId = pollIdParam ? Number(pollIdParam) : NaN; + const isValidPollId = !!pollIdParam && !Number.isNaN(pollId); const navigate = useNavigate(); const { @@ -19,7 +21,7 @@ export default function DebateVoteResultPage() { isRefetching, refetch, isRefetchError, - } = useGetPollInfo(pollId); + } = useGetPollInfo(pollId, { enabled: isValidPollId }); const handleGoHome = () => { navigate('/'); }; @@ -53,7 +55,7 @@ export default function DebateVoteResultPage() { } }; - if (!pollIdParam || Number.isNaN(pollId)) { + if (isValidPollId) { return ( diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.tsx index 92e2b4e4..0f5c5319 100644 --- a/src/page/VoteParticipationPage/VoteParticipationPage.tsx +++ b/src/page/VoteParticipationPage/VoteParticipationPage.tsx @@ -20,8 +20,9 @@ const TEAM_LABEL = { export default function VoteParticipationPage() { const { id: pollIdParam } = useParams(); const navigate = useNavigate(); - console.log('pollIdParam', pollIdParam); + // 1) pollId 파싱 + 유효성 체크 const pollId = pollIdParam ? Number(pollIdParam) : NaN; + const isValidPollId = !!pollIdParam && !Number.isNaN(pollId); const [participantName, setParticipantName] = useState(''); const [selectedTeam, setSelectedTeam] = useState(null); @@ -33,7 +34,7 @@ export default function VoteParticipationPage() { isRefetching, refetch, isRefetchError, - } = useGetVoterPollInfo(pollId); + } = useGetVoterPollInfo(pollId, { enabled: isValidPollId }); const { openModal, closeModal, ModalWrapper } = useModal(); const isSubmitDisabled = @@ -69,7 +70,7 @@ export default function VoteParticipationPage() { ); } - if (!pollIdParam || Number.isNaN(pollId)) { + if (!isValidPollId) { return ( From 6dfae79798822b22e451a948859d0079556b3255 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 22:12:48 +0900 Subject: [PATCH 30/39] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EA=B3=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=AA=85=20=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/{VoteResultRow.tsx => VoteBar.tsx} | 0 .../DebateVoteResultPage/components/VoteDetailResult.tsx | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/page/DebateVoteResultPage/components/{VoteResultRow.tsx => VoteBar.tsx} (100%) diff --git a/src/page/DebateVoteResultPage/components/VoteResultRow.tsx b/src/page/DebateVoteResultPage/components/VoteBar.tsx similarity index 100% rename from src/page/DebateVoteResultPage/components/VoteResultRow.tsx rename to src/page/DebateVoteResultPage/components/VoteBar.tsx diff --git a/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx b/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx index 4ad14e9a..64eae086 100644 --- a/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx +++ b/src/page/DebateVoteResultPage/components/VoteDetailResult.tsx @@ -1,6 +1,6 @@ // pages/VoteDetailResult.tsx import { motion } from 'framer-motion'; -import VoteResultRow from './VoteResultRow'; +import VoteBar from './VoteBar'; type VoteDetailResultProps = { onGoHome?: () => void; @@ -28,13 +28,13 @@ export default function VoteDetailResult({

- - Date: Sat, 11 Oct 2025 22:14:06 +0900 Subject: [PATCH 31/39] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EA=B3=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=AA=85=20=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/{CountUp.tsx => AnimatedCounter.tsx} | 0 src/page/DebateVoteResultPage/components/VoteBar.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/page/DebateVoteResultPage/components/{CountUp.tsx => AnimatedCounter.tsx} (100%) diff --git a/src/page/DebateVoteResultPage/components/CountUp.tsx b/src/page/DebateVoteResultPage/components/AnimatedCounter.tsx similarity index 100% rename from src/page/DebateVoteResultPage/components/CountUp.tsx rename to src/page/DebateVoteResultPage/components/AnimatedCounter.tsx diff --git a/src/page/DebateVoteResultPage/components/VoteBar.tsx b/src/page/DebateVoteResultPage/components/VoteBar.tsx index 9797afe5..2a6c6439 100644 --- a/src/page/DebateVoteResultPage/components/VoteBar.tsx +++ b/src/page/DebateVoteResultPage/components/VoteBar.tsx @@ -1,5 +1,5 @@ import { motion } from 'framer-motion'; -import AnimatedCounter from './CountUp'; +import AnimatedCounter from './AnimatedCounter'; import { TEAM_STYLE, TeamKey } from '../../../type/type'; type VoteBarProps = { From 7e94cb06b2ead2eaa628bf19bbffd9db7e6919e5 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 22:24:26 +0900 Subject: [PATCH 32/39] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=EA=B3=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=EB=AA=85=20=EC=9D=BC=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mutations/useFetchEndpoll.ts | 2 +- src/page/DebateVotePage/DebateVotePage.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/mutations/useFetchEndpoll.ts b/src/hooks/mutations/useFetchEndpoll.ts index 9d1b4efc..53574760 100644 --- a/src/hooks/mutations/useFetchEndpoll.ts +++ b/src/hooks/mutations/useFetchEndpoll.ts @@ -2,7 +2,7 @@ import { patchEndPoll } from '../../apis/apis/poll'; import { PatchPollResponseType } from '../../apis/responses/poll'; import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; -export default function useFetchEndpoll(onSuccess: (id: number) => void) { +export default function useFetchEndPoll(onSuccess: (id: number) => void) { return usePreventDuplicateMutation({ mutationFn: (pollId: number) => patchEndPoll(pollId), onSuccess: (response: PatchPollResponseType) => { diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx index 55db5a26..c2c1d587 100644 --- a/src/page/DebateVotePage/DebateVotePage.tsx +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -4,7 +4,7 @@ import { QRCodeSVG } from 'qrcode.react'; import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; import { useGetPollInfo } from '../../hooks/query/useGetPollInfo'; import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; -import useFetchEndpoll from '../../hooks/mutations/useFetchEndpoll'; +import useFetchEndPoll from '../../hooks/mutations/useFetchEndpoll'; export default function DebateVotePage() { const { id: pollIdParam } = useParams(); const pollId = pollIdParam ? Number(pollIdParam) : NaN; @@ -33,7 +33,7 @@ export default function DebateVotePage() { refetch, isRefetchError, } = useGetPollInfo(pollId, { refetchInterval: 5000, enabled: isValidPollId }); - const { mutate } = useFetchEndpoll(handleGoToResult); + const { mutate } = useFetchEndPoll(handleGoToResult); const participants = data?.voterNames; const isLoading = isFetching || isRefetching; From 2588e21ea054ad5ff19cf06fb9419ac78aab6246 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 22:43:24 +0900 Subject: [PATCH 33/39] =?UTF-8?q?refactor:=20layout=20=EC=A4=91=EB=B3=B5?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DebateEndPage/DebateEndPage.stories.tsx | 25 ++++++ src/page/DebateEndPage/DebateEndPage.tsx | 83 +++++++++---------- 2 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 src/page/DebateEndPage/DebateEndPage.stories.tsx diff --git a/src/page/DebateEndPage/DebateEndPage.stories.tsx b/src/page/DebateEndPage/DebateEndPage.stories.tsx new file mode 100644 index 00000000..e7d27b01 --- /dev/null +++ b/src/page/DebateEndPage/DebateEndPage.stories.tsx @@ -0,0 +1,25 @@ +import { Meta, StoryObj } from '@storybook/react'; +import DebateEndPage from './DebateEndPage'; + +const meta: Meta = { + title: 'page/DebateEndPage', + component: DebateEndPage, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 + route: '/table/customize/123/end/vote', + routePattern: '/table/customize/:id/end/vote', + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/page/DebateEndPage/DebateEndPage.tsx b/src/page/DebateEndPage/DebateEndPage.tsx index c78066e7..f62de766 100644 --- a/src/page/DebateEndPage/DebateEndPage.tsx +++ b/src/page/DebateEndPage/DebateEndPage.tsx @@ -4,7 +4,6 @@ import clapImage from '../../assets/debateEnd/clap.png'; import feedbackTimerImage from '../../assets/debateEnd/feedback_timer.png'; import voteStampImage from '../../assets/debateEnd/vote_stamp.png'; import GoToHomeButton from '../../components/GoToHomeButton/GoToHomeButton'; -import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; import usePostPoll from '../../hooks/mutations/useCreatePoll'; import MenuCard from './components/MenuCard'; @@ -26,50 +25,46 @@ export default function DebateEndPage() { }; return ( - - -
-
-

- 토론을 모두 마치셨습니다 -

- 박수 -
+
+
+

+ 토론을 모두 마치셨습니다 +

+ 박수 +
-
- +
+ - { - if (!tableId) return; // NaN 방지 - mutate(Number(tableId)); - }} - ariaLabel="승패투표 생성 및 진행" - /> -
-
- -
-
- - + { + if (!tableId) return; // NaN 방지 + mutate(Number(tableId)); + }} + ariaLabel="승패투표 생성 및 진행" + /> +
+
+ +
+
); } From ba9b81577b24c769726536c57fe4f45dea72a116 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 23:22:10 +0900 Subject: [PATCH 34/39] =?UTF-8?q?fix:=20pollId=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EB=AC=B8=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/DebateVoteResultPage/DebateVoteResultPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx index 9014b926..69c19682 100644 --- a/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx +++ b/src/page/DebateVoteResultPage/DebateVoteResultPage.tsx @@ -55,7 +55,7 @@ export default function DebateVoteResultPage() { } }; - if (isValidPollId) { + if (!isValidPollId) { return ( From 7cd31011d76c39a1471f85d4888cfd7968c588a0 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 23:22:45 +0900 Subject: [PATCH 35/39] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20export=EB=AC=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/type.ts b/src/type/type.ts index 1a24b4ed..8c759a3c 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -82,7 +82,7 @@ export interface VoterPollInfo { participateCode: string; team: 'PROS' | 'CONS'; } -export // ===== 배경 색상 상태 타입 및 컬러 맵 정의 ===== + type TimerBGState = 'default' | 'warning' | 'danger' | 'expired'; export const bgColorMap: Record = { default: '', From bd4cb28d2169db81153256ee6fcc92ffcc345434 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sat, 11 Oct 2025 23:52:16 +0900 Subject: [PATCH 36/39] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=83=81=ED=83=9C=EB=B3=80=EA=B2=BD=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/page/VoteParticipationPage/VoteParticipationPage.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/page/VoteParticipationPage/VoteParticipationPage.tsx b/src/page/VoteParticipationPage/VoteParticipationPage.tsx index 0f5c5319..c786aabc 100644 --- a/src/page/VoteParticipationPage/VoteParticipationPage.tsx +++ b/src/page/VoteParticipationPage/VoteParticipationPage.tsx @@ -52,9 +52,6 @@ export default function VoteParticipationPage() { team: selectedTeam, }, }); - - setParticipantName(''); - setSelectedTeam(null); }; const isLoading = isFetching || isRefetching; From d2a24aa312b9556346b2c57f06ca0f9f9553932f Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sun, 12 Oct 2025 23:08:46 +0900 Subject: [PATCH 37/39] =?UTF-8?q?fix:=20=EC=82=AD=EC=A0=9C=EB=90=9C=20expo?= =?UTF-8?q?rt=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/type/type.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type/type.ts b/src/type/type.ts index 8c759a3c..f96e5f7e 100644 --- a/src/type/type.ts +++ b/src/type/type.ts @@ -83,7 +83,7 @@ export interface VoterPollInfo { team: 'PROS' | 'CONS'; } -type TimerBGState = 'default' | 'warning' | 'danger' | 'expired'; +export type TimerBGState = 'default' | 'warning' | 'danger' | 'expired'; export const bgColorMap: Record = { default: '', warning: 'bg-brand', // 30초~11초 구간 From 0dfed8698304875f2a221b6274df6e43e70623d9 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sun, 12 Oct 2025 23:14:15 +0900 Subject: [PATCH 38/39] =?UTF-8?q?fix:=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20?= =?UTF-8?q?=EB=8C=80=EC=86=8C=EB=AC=B8=EC=9E=90=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mutations/useFetchEndPoll.ts | 12 ++++++++++++ src/page/DebateVotePage/DebateVotePage.tsx | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/hooks/mutations/useFetchEndPoll.ts diff --git a/src/hooks/mutations/useFetchEndPoll.ts b/src/hooks/mutations/useFetchEndPoll.ts new file mode 100644 index 00000000..53574760 --- /dev/null +++ b/src/hooks/mutations/useFetchEndPoll.ts @@ -0,0 +1,12 @@ +import { patchEndPoll } from '../../apis/apis/poll'; +import { PatchPollResponseType } from '../../apis/responses/poll'; +import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; + +export default function useFetchEndPoll(onSuccess: (id: number) => void) { + return usePreventDuplicateMutation({ + mutationFn: (pollId: number) => patchEndPoll(pollId), + onSuccess: (response: PatchPollResponseType) => { + onSuccess(response.id); + }, + }); +} diff --git a/src/page/DebateVotePage/DebateVotePage.tsx b/src/page/DebateVotePage/DebateVotePage.tsx index c2c1d587..dd08bcb1 100644 --- a/src/page/DebateVotePage/DebateVotePage.tsx +++ b/src/page/DebateVotePage/DebateVotePage.tsx @@ -4,7 +4,7 @@ import { QRCodeSVG } from 'qrcode.react'; import DefaultLayout from '../../layout/defaultLayout/DefaultLayout'; import { useGetPollInfo } from '../../hooks/query/useGetPollInfo'; import ErrorIndicator from '../../components/ErrorIndicator/ErrorIndicator'; -import useFetchEndPoll from '../../hooks/mutations/useFetchEndpoll'; +import useFetchEndPoll from '../../hooks/mutations/useFetchEndPoll'; export default function DebateVotePage() { const { id: pollIdParam } = useParams(); const pollId = pollIdParam ? Number(pollIdParam) : NaN; From a08add3988b24728e381c550c2013ce6d85a4229 Mon Sep 17 00:00:00 2001 From: jaeml06 Date: Sun, 12 Oct 2025 23:19:26 +0900 Subject: [PATCH 39/39] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/mutations/useFetchEndpoll.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/hooks/mutations/useFetchEndpoll.ts diff --git a/src/hooks/mutations/useFetchEndpoll.ts b/src/hooks/mutations/useFetchEndpoll.ts deleted file mode 100644 index 53574760..00000000 --- a/src/hooks/mutations/useFetchEndpoll.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { patchEndPoll } from '../../apis/apis/poll'; -import { PatchPollResponseType } from '../../apis/responses/poll'; -import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; - -export default function useFetchEndPoll(onSuccess: (id: number) => void) { - return usePreventDuplicateMutation({ - mutationFn: (pollId: number) => patchEndPoll(pollId), - onSuccess: (response: PatchPollResponseType) => { - onSuccess(response.id); - }, - }); -}