From 8f6bec751fadcd5bbdc3ffba5865e7bc2ff71e6b Mon Sep 17 00:00:00 2001 From: Kakabay <2kakabayashyrberdyew@gmail.com> Date: Tue, 3 Sep 2024 17:35:46 +0500 Subject: [PATCH] voting page --- components/vote/Countdown.tsx | 6 +- components/vote/ParticipantCard.tsx | 90 ++++++----- components/vote/ParticipantsList.tsx | 216 ++++++++++++++++++++------- 3 files changed, 224 insertions(+), 88 deletions(-) diff --git a/components/vote/Countdown.tsx b/components/vote/Countdown.tsx index bcdae19..c992c00 100644 --- a/components/vote/Countdown.tsx +++ b/components/vote/Countdown.tsx @@ -1,13 +1,14 @@ -import { useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { differenceInSeconds, parseISO, addHours } from 'date-fns'; import GradientTitle from './GradientTitle'; interface CountdownProps { startsAt: string; endsAt: string; + setVoteStatus: Dispatch>; } -const Countdown: React.FC = ({ startsAt, endsAt }) => { +const Countdown: React.FC = ({ startsAt, endsAt, setVoteStatus }) => { const [timeLeft, setTimeLeft] = useState(''); const [eventStatus, setEventStatus] = useState('Finished'); @@ -30,6 +31,7 @@ const Countdown: React.FC = ({ startsAt, endsAt }) => { difference = differenceInSeconds(endsAtDate, nowUTC5); } else { setEventStatus('Finished'); + setVoteStatus('closed'); return; } diff --git a/components/vote/ParticipantCard.tsx b/components/vote/ParticipantCard.tsx index b11b50c..4ff22a8 100644 --- a/components/vote/ParticipantCard.tsx +++ b/components/vote/ParticipantCard.tsx @@ -29,12 +29,14 @@ const ParticipantCard = ({ const substractedProgress = progress > 99 ? progress - 2 : progress; return winner && votes !== 0 ? ( -
+ ////////////////////////////////////////////// Winner card +

{number}

{photo && name ? ( + // If there an image, show image
) : ( -
+ // If there is no image, show placeholder +
{name ? ( -

+

{name}

) : null} -

- {votes ? votes : 0} ses + + {/* If we have voteCode and voting not closed, then show badge with code. Else dont show */} + {voteCode && voteStatus !== 'closed' ? ( + // Desktop version +

+ Ses bermek üçin{' '} + + {voteCode} + + ugrat +

+ ) : null} +

+
+

+ {votes ? votes : 0} +

+

+ ses

- {voteCode && voteStatus !== 'closed' ? ( -

- Ses bermek üçin{' '} - - {voteCode} - - ugrat -

- ) : null}
-
+ + {/* Progress bar */} + {/*
-
+
*/}
+ + {/* If we have voteCode and voting not closed, then show badge with code. Else dont show */} {voteCode && voteStatus !== 'closed' ? ( + // Mobile version

Ses bermek üçin{' '} @@ -99,6 +115,7 @@ const ParticipantCard = ({ ) : null}

) : ( + ////////////////////////////////////////////// Simple card

{number}

@@ -127,36 +144,39 @@ const ParticipantCard = ({ )}
-
+
{name ? (

{name}

) : null} -

- {votes ? votes : 0} ses + {/* If we have voteCode and voting not closed, then show badge with code. Else dont show */} + {voteCode && voteStatus !== 'closed' ? ( + // Desktop version +

+ Ses bermek üçin + + {voteCode} + + ugrat +

+ ) : null} +

+
+

+ {votes ? votes : 0} +

+

+ ses

- {voteCode && voteStatus !== 'closed' ? ( -

- Ses bermek üçin - - {voteCode} - - ugrat -

- ) : null} -
-
-
+ + {/* If we have voteCode and voting not closed, then show badge with code. Else dont show */} {voteCode && voteStatus !== 'closed' ? ( + // Mobile version

Ses bermek üçin{' '} diff --git a/components/vote/ParticipantsList.tsx b/components/vote/ParticipantsList.tsx index fd0e46d..276c384 100644 --- a/components/vote/ParticipantsList.tsx +++ b/components/vote/ParticipantsList.tsx @@ -11,16 +11,31 @@ import PageBage from './PageBage'; import Image from 'next/image'; import { useMediaQuery } from 'usehooks-ts'; import Countdown from './Countdown'; +import Link from 'next/link'; interface IParams { vote_id?: string; } +interface ISocketMessage { + voting_id: number; + voting_item_id: number; + client_id: number; + message: string; + date: string; +} + const ParticipantsList = ({ vote_id }: IParams) => { const [data, setData] = useState(); - const [participantsData, setParticipantsData] = useState(); + const [participantsData, setParticipantsData] = useState([]); const [voteStatus, setVoteStatus] = useState(); const [winnersCount, setWinnersCount] = useState(0); + + // States realted to web socket + const [smsNumber, setSmsNumber] = useState(null); + const [socket, setSocket] = useState(null); + const [isConnected, setIsConnected] = useState(false); + const mobile = useMediaQuery('(max-width: 768px)'); const { setVoteDescription } = useContext(VoteContext).voteDescriptionContext; @@ -29,9 +44,10 @@ const ParticipantsList = ({ vote_id }: IParams) => { if (!vote_id) { Queries.getAllVotes().then((res) => { setData(res); - setParticipantsData(res.data.voting_items); + setParticipantsData([...res.data.voting_items]); setVoteDescription(res.data.description); setVoteStatus(res.data.status); + setSmsNumber(res.data.sms_number); }); } else { Queries.getVote(vote_id).then((res) => { @@ -39,40 +55,120 @@ const ParticipantsList = ({ vote_id }: IParams) => { setParticipantsData(res.data.voting_items); setVoteDescription(res.data.description); setVoteStatus(res.data.status); + setSmsNumber(res.data.sms_number); }); } - if (voteStatus !== 'closed') { - const interval = setInterval(() => { - if (!vote_id) { - Queries.getAllVotes().then((res) => { - setParticipantsData(res.data.voting_items); - setVoteStatus(res.data.status); - }); - } else { - Queries.getVote(vote_id).then((res) => { - setParticipantsData(res.data.voting_items); - setVoteStatus(res.data.status); - }); - } - }, 10000); - return () => clearInterval(interval); - } - - // if (voteStatus !== 'closed' && vote_id) { - // const interval = setInterval(() => { - // Queries.getVote(vote_id).then((res) => { - // setParticipantsData(res.data.voting_items); - // setVoteStatus(res.data.status); - // }); - // }, 10000); - // return () => clearInterval(interval); - // } - if (participantsData) { winnersCountHandle(participantsData); } - }, [voteStatus]); + }, []); + + useEffect(() => { + let socket: WebSocket | null = null; + let reconnectTimeout: NodeJS.Timeout | null = null; + let pingInterval: NodeJS.Timeout | null = null; + + const connectWebSocket = () => { + try { + socket = new WebSocket(`wss://sms.turkmentv.gov.tm/ws/voting?dst=${smsNumber}`); + setSocket(socket); + + socket.onopen = () => { + console.log('WebSocket is connected'); + setIsConnected(true); + + pingInterval = setInterval(() => { + if (socket?.readyState === WebSocket.OPEN) { + try { + socket.send(JSON.stringify({ type: 'ping' })); + } catch (error) { + console.error('Error sending ping:', error); + } + } + }, 25000); // Ping every 25 seconds + }; + + socket.onmessage = (event) => { + try { + // console.log('Message received from WebSocket:', event.data); + const message = JSON.parse(event.data); + handleWebSocketMessage(message); + } catch (error) { + console.error('Error processing message:', error); + } + }; + + socket.onerror = (error) => { + console.error('WebSocket error:', error); + }; + + socket.onclose = () => { + console.log('WebSocket is closed'); + setIsConnected(false); + + if (pingInterval) { + clearInterval(pingInterval); + } + + if (!reconnectTimeout) { + reconnectTimeout = setTimeout(() => { + console.log('Attempting to reconnect WebSocket...'); + connectWebSocket(); + }, 5000); // Reconnect after 5 seconds + } + }; + } catch (error) { + console.error('WebSocket connection error:', error); + } + }; + + if (smsNumber) { + connectWebSocket(); + } + + return () => { + if (socket) { + socket.close(); + } + if (reconnectTimeout) { + clearTimeout(reconnectTimeout); + } + if (pingInterval) { + clearInterval(pingInterval); + } + }; + }, [smsNumber]); + + const handleWebSocketMessage = (message: ISocketMessage) => { + setParticipantsData((prevVotingItems) => { + if (!prevVotingItems) return []; + + // Update the corresponding voting item + const updatedItems = prevVotingItems.map((item, index) => + item.id === message.voting_item_id + ? { ...item, votes_count: item.votes_count + 10000 } + : item, + ); + + // Sort the updated items array by votes_count in descending order + return updatedItems.sort((a, b) => b.votes_count - a.votes_count); + }); + }; + + const addVotes = () => { + setParticipantsData((prevVotingItems) => { + if (!prevVotingItems) return []; + + // Update the corresponding voting item + const updatedItems = prevVotingItems.map((item, index) => + index === 5 ? { ...item, votes_count: item.votes_count + 10000 } : item, + ); + + // Sort the updated items array by votes_count in descending order + return updatedItems.sort((a, b) => b.votes_count - a.votes_count); + }); + }; const winnersCountHandle = (winners: VotingItem[]) => { let count = 0; @@ -126,33 +222,51 @@ const ParticipantsList = ({ vote_id }: IParams) => { {/* {data.data.title ? : null} */} {data.data.ends_at && data.data.starts_at ? ( - + ) : null}

{winnersCount > 1 ? : null} - {participantsData && - participantsData[0].votes_count > 0 && - participantsData[0].votes_percents === 100 ? ( + {participantsData && participantsData[0].votes_count > 0 ? (
{participantsData.map((participant, id) => - participant.votes_percents === 100 && - participant.votes_count === participantsData[0].votes_count && - voteStatus ? ( - + participant.votes_count === participantsData[0].votes_count && voteStatus ? ( + participant.url ? ( + + + + ) : ( + + ) ) : null, )}
@@ -162,7 +276,7 @@ const ParticipantsList = ({ vote_id }: IParams) => {
{participantsData ? participantsData.map((participant, id) => - participant.votes_percents !== 100 && voteStatus ? ( + participant.id !== participantsData[0].id && voteStatus ? (