@@ -3,149 +3,52 @@ import Button from '@/components/Button/Button';
33import CandidateGroup from '@/components/Candidate/CandidateGroup' ;
44import Modal from '@/components/Modal/Modal' ;
55import Loading from '@/components/Loading/Loading' ;
6- import { useToast } from '@/hook/useToast' ;
7- import { useEffect , useState } from 'react' ;
8- import useIsMobile from '@/hook/useIsMobile' ;
9- import { useNavigate } from 'react-router-dom' ;
10- import {
11- getCandidateLatests ,
12- getCandidateLatestResponse ,
13- } from '@/apis/candidate/getCandidateLatest' ;
6+ import { useMediaQuery } from 'react-responsive' ;
147import { ICONS } from '@/constants/iconPath' ;
15- import { updateVoteCount } from '@/apis/vote/updateVoteCount' ;
16- import { postVoteResult } from '@/apis/vote/postVoteResult' ;
17- import { AxiosError } from 'axios' ;
8+ import { useVote } from '@/hook/useVote' ;
9+ import { useCallback , useMemo } from 'react' ;
1810
1911const Vote = ( ) => {
20- const userId = Number ( localStorage . getItem ( 'userId' ) ) ;
21- const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
22- const [ pollData , setPollData ] = useState < { [ pollId : number ] : getCandidateLatestResponse [ ] } > ( { } ) ;
23- const [ pollIds , setPollIds ] = useState < number [ ] > ( [ ] ) ;
24- const [ pageIndex , setPageIndex ] = useState ( 0 ) ;
25- const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
26- const [ selectedCandidates , setSelectedCandidates ] = useState < { [ pollId : number ] : number | null } > (
27- { } ,
12+ const isMobile = useMediaQuery ( { query : '(max-width: 768px)' } ) ;
13+ const {
14+ pollData,
15+ pollIds,
16+ pageIndex,
17+ setPageIndex,
18+ isSubmitting,
19+ isModalOpen,
20+ setIsModalOpen,
21+ selectedCandidates,
22+ handleSelect,
23+ handleSubmit,
24+ } = useVote ( ) ;
25+
26+ const currentPollId = useMemo ( ( ) => pollIds [ pageIndex ] , [ pollIds , pageIndex ] ) ;
27+
28+ const currentCandidates = useMemo ( ( ) => pollData [ currentPollId ] || [ ] , [ pollData , currentPollId ] ) ;
29+
30+ const selectedCandidateId = useMemo (
31+ ( ) => selectedCandidates [ currentPollId ] ?? null ,
32+ [ selectedCandidates , currentPollId ] ,
2833 ) ;
29- const navigate = useNavigate ( ) ;
30- const isMobile = useIsMobile ( ) ;
31- const { showToast } = useToast ( ) ;
3234
33- useEffect ( ( ) => {
34- async function fetchData ( ) {
35- try {
36- const data = await getCandidateLatests ( ) ;
37- const groupedData : { [ pollId : number ] : getCandidateLatestResponse [ ] } = { } ;
38-
39- data . forEach ( ( item ) => {
40- if ( ! groupedData [ item . pollId ] ) {
41- groupedData [ item . pollId ] = [ ] ;
42- }
43- groupedData [ item . pollId ] . push ( item ) ;
44- } ) ;
45-
46- const ids : number [ ] = Object . keys ( groupedData )
47- . map ( Number )
48- . sort ( ( a , b ) => a - b ) ;
49-
50- setPollData ( groupedData ) ;
51- setPollIds ( ids ) ;
52- setPageIndex ( 0 ) ;
53- } catch ( error ) {
54- navigate ( '/main' ) ;
55- if ( error instanceof AxiosError ) {
56- if ( error . status === 404 ) {
57- showToast ( error . message , 'warning' ) ;
58- } else {
59- const message =
60- typeof error . response ?. data === 'string'
61- ? error . response . data
62- : JSON . stringify ( error . response ?. data ) ;
63-
64- showToast ( message , 'warning' ) ;
65- }
66- } else {
67- showToast ( String ( error ) , 'warning' ) ;
68- }
69- console . error ( error ) ;
70- }
71- }
72-
73- fetchData ( ) ;
74- } , [ ] ) ;
75-
76- const handlePrev = ( ) => {
77- setPageIndex ( ( prev ) => Math . max ( prev - 1 , 0 ) ) ;
78- } ;
79-
80- const handleMain = ( ) => {
81- navigate ( '/main' ) ;
82- } ;
83-
84- const handleNext = ( ) => {
85- setPageIndex ( ( prev ) => Math . min ( prev + 1 , pollIds . length - 1 ) ) ;
86- } ;
87-
88- const handleConfirm = async ( ) => {
89- setIsModalOpen ( false ) ;
90-
91- if ( ! userId ) {
92- navigate ( '/' ) ;
93- showToast ( '로그인이 필요합니다.' , 'warning' ) ;
94- return ;
95- }
96-
97- const voteResults = Object . entries ( selectedCandidates ) . map ( ( [ pollId , candidateId ] ) => ( {
98- pollId : Number ( pollId ) ,
99- candidateId : candidateId ! ,
100- } ) ) ;
101-
102- try {
103- setIsSubmitting ( true ) ;
104- await Promise . all ( voteResults . map ( updateVoteCount ) ) ;
105- await postVoteResult ( { userId } ) ;
106- localStorage . setItem ( 'voted' , 'true' ) ;
107- navigate ( '/main' ) ;
108- showToast ( '투표가 성공적으로 완료되었습니다.' , 'success' ) ;
109- } catch ( error ) {
110- navigate ( '/main' ) ;
111- if ( error instanceof AxiosError ) {
112- if ( error . status === 404 ) {
113- showToast ( error . message , 'warning' ) ;
114- } else {
115- const message =
116- typeof error . response ?. data === 'string'
117- ? error . response . data
118- : JSON . stringify ( error . response ?. data ) ;
119-
120- showToast ( message , 'warning' ) ;
121- }
122- } else {
123- showToast ( String ( error ) , 'warning' ) ;
124- }
125- console . error ( error ) ;
126- } finally {
127- setIsSubmitting ( false ) ;
128- }
129- } ;
35+ const questionText = currentCandidates [ 0 ] ?. questionText ;
36+ const icon = currentCandidates [ 0 ] ?. icon ;
37+ const isCandidateSelected = selectedCandidateId !== null ;
13038
131- const handleCandidateSelect = ( pollId : number , candidateId : number ) => {
132- setSelectedCandidates ( ( prev ) => ( { ...prev , [ pollId ] : candidateId } ) ) ;
133- } ;
39+ const handlePrev = useCallback ( ( ) => setPageIndex ( ( prev ) => Math . max ( prev - 1 , 0 ) ) , [ ] ) ;
40+ const handleNext = useCallback (
41+ ( ) => setPageIndex ( ( prev ) => Math . min ( prev + 1 , pollIds . length - 1 ) ) ,
42+ [ pollIds . length ] ,
43+ ) ;
13444
135- if ( pollIds . length === 0 ) {
45+ if ( ! pollIds . length ) {
13646 return (
13747 < div className = "h-full flex items-center justify-center" >
13848 < Loading />
13949 </ div >
14050 ) ;
14151 }
142- const currentPollId = pollIds [ pageIndex ] ;
143- const currentCandidates = pollData [ currentPollId ] ;
144- const questionText = currentCandidates [ 0 ] ?. questionText ;
145- const icon = currentCandidates [ 0 ] ?. icon ;
146-
147- const selectedCandidateId = selectedCandidates [ currentPollId ] ?? null ;
148- const isCandidateSelected = selectedCandidateId !== null ;
14952
15053 return (
15154 < div className = "h-full overflow-hidden bg-white flex items-center flex-col justify-center p-5" >
@@ -170,15 +73,15 @@ const Vote = () => {
17073 userName : c . userName ,
17174 } ) ) }
17275 selectedCandidateId = { selectedCandidateId }
173- onSelect = { ( candidateId ) => handleCandidateSelect ( currentPollId , candidateId ) }
76+ onSelect = { ( candidateId ) => handleSelect ( currentPollId , candidateId ) }
17477 />
17578 </ div >
17679 </ div >
17780 < div className = "flex justify-between items-center w-full p-5" >
17881 { pageIndex > 0 ? (
17982 < Button label = "이전" onClick = { handlePrev } type = "outline" size = "sm" />
18083 ) : (
181- < Button label = "이전" onClick = { handleMain } type = "outline" size = "sm" />
84+ < Button label = "이전" onClick = { ( ) => setPageIndex ( 0 ) } type = "outline" size = "sm" />
18285 ) }
18386
18487 { pageIndex < pollIds . length - 1 ? (
@@ -197,7 +100,7 @@ const Vote = () => {
197100 < div className = "p-5 " >
198101 < div className = "flex flex-col items-center justify-center bg-gray-50 p-5 rounded-[30px] md:flex-row min-h-[400px] min-w-[1030px]" >
199102 < div className = "flex flex-1 p-13 min-h-[400px] min-w-[500px] items-center justify-center" >
200- < div className = "flex flex-col items-center justify-center gap-10 font-ps text-xl text-center " >
103+ < div className = "flex flex-col items-center justify-center gap-10 font-ps text-xl text-center" >
201104 < p className = "break-all" > { questionText } </ p >
202105 < img
203106 src = { ICONS . QUESTION_ICON ( icon ) }
@@ -213,15 +116,15 @@ const Vote = () => {
213116 userName : c . userName ,
214117 } ) ) }
215118 selectedCandidateId = { selectedCandidateId }
216- onSelect = { ( candidateId ) => handleCandidateSelect ( currentPollId , candidateId ) }
119+ onSelect = { ( id ) => handleSelect ( currentPollId , id ) }
217120 />
218121 </ div >
219122 </ div >
220123 < div className = "flex justify-between items-center w-full mt-5 mb-5" >
221124 { pageIndex > 0 ? (
222125 < Button label = "이전" onClick = { handlePrev } type = "outline" size = "lg" />
223126 ) : (
224- < Button label = "이전" onClick = { handleMain } type = "outline" size = "lg" />
127+ < Button label = "이전" onClick = { ( ) => setPageIndex ( 0 ) } type = "outline" size = "lg" />
225128 ) }
226129
227130 { pageIndex < pollIds . length - 1 ? (
@@ -241,9 +144,10 @@ const Vote = () => {
241144 < Modal
242145 isOpen = { isModalOpen }
243146 setIsOpen = { setIsModalOpen }
244- onConfirm = { handleConfirm }
147+ onConfirm = { handleSubmit }
245148 text2 = "한번 완료하면 다시 투표할 수 없어요"
246149 />
150+
247151 { isSubmitting && (
248152 < div className = "fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-30" >
249153 < Loading />
0 commit comments